From 4682cd3abe59ffda1272b6eec5b7fbf7e6a410dd Mon Sep 17 00:00:00 2001 From: Zonglei Dong Date: Mon, 12 Jul 2021 16:11:26 +0800 Subject: [PATCH] Merge 1.3.0 to develop. (#430) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * merge develop into 1.3.0 branch (#401) * upgrade gradle to 6.8.3 * [ISSUE #265]rename wemq and access to eventmesh * [ISSUE #265]rename proxy field to eventmesh * merge conflict * remove warn * [ISSUE #265] Specification of code structure and file naming * remove eventmesh-registry module * enabled http/tcp monitor logs * Update README.md * Changed com.webank to org.apache Changed 'eventmesh-connector-api' code package name to apache. * issue #277:refactor eventmesh-common package with org.apache * issue #277:rename package with org.apache * Refactor 'eventmesh-connector-rocketmq' package name to org.apache * Update README.zh-CN.md * [ISSUE #282]Refactor 'eventmesh-starter' package name to org.apache * Update codeStyle.xml * Refactor 'eventmesh-test' package name to org.apache #283 * Refactor 'eventmesh-test' package name to org.apache #283 * Refactor 'eventmesh-test' package name to org.apache #283 * Refactor 'eventmesh-test' package name to org.apache #283 * Refactor 'eventmesh-test' package name to org.apache #283 * refactor runtime module package com.webank to org.apache * refactor(eventmesh-sdk-java):rename to org.apache(#281) * [ISSUE #281]refactor(eventmesh-sdk-java):rename to org.apache * add licenses of apache for runtime module * add NOTICE * bugfix for event-mesh-test module * change package name to org.apache * format README.md * change package name to org.apache * fix ISSUE #296:add licenses in each source file under the eventmesh-sdk-java * [ISSUE #294]add licenses in file under eventmesh-connector-rocketmq module * [ISSUE #293]Lack of licenses in each source file under the eventmesh-connector-api module * [ISSUE #298]Lack of licenses in each source file under the eventmesh-test module * [ISSUE #297]Lack of licenses in each source file under the eventmesh-starter module * Create .asf.yaml * Update .asf.yaml * Update .asf.yaml (#316) * Update README.md * Update .asf.yaml * bugfix build.gradle tar task (#318) Co-authored-by: jonyang(杨军) * [ISSUE #322] Rename package name "com.webank.eventmesh" to "org.apache.eventmesh" (#319) * rename org.apache.runtime to com.webank.runtime * rename com.webank.eventmesh to org.apache.eventmesh * fix(docs): change the travis location * Create DISCLAIMER-WIP * Delete CNAME * Delete _config.yml * Delete package.json * Add files via upload * Add files via upload * Delete eventmesh-multi-runtime.jpg * Update README.md * Update eventmesh-runtime-quickstart.md * Update README.zh-CN.md * Update eventmesh-runtime-quickstart.zh-CN.md * [ISSUE #325]Update gradle configuration for publishing package to maven repository (#326) * [ISSUE #325]Update gradle configuration for publishing package to maven repository * update build.gradle * update build.gradle and gradle.properties * upgrade gradle to 7.0 and fix test bug. (#327) * bugfix build.gradle tar task * merge * upgrade to gradle 7.0 * bugfix gradle task spotbugs * bugfix eventmesh-connector-rocketmq testImplementation * upgrade to gradle7.0 * refactor runtime module test and spotbugs error * bugfix sign task Co-authored-by: jonyang(杨军) Co-authored-by: jonyangx * remove unused files Signed-off-by: qqeasonchen * update build.gradle and gradle.properties for publish to maven repository (#330) * [ISSUE #325]Update gradle configuration for publishing package to maven repository * update build.gradle * update build.gradle and gradle.properties * update build.gradle and gradle.properties for publish to maven repository * Update README.md * Update README.zh-CN.md * update quickstart md files for gradle version (#332) * [ISSUE #325]Update gradle configuration for publishing package to maven repository * update build.gradle * update build.gradle and gradle.properties * update build.gradle and gradle.properties for publish to maven repository * * update gradle version for instructions * fix: dist task exception * remove dead docs Signed-off-by: qqeasonchen * [ISSUE #329]Missing Log4j dependency (#336) * [ISSUE #325]Update gradle configuration for publishing package to maven repository * update build.gradle * update build.gradle and gradle.properties * update build.gradle and gradle.properties for publish to maven repository * * update gradle version for instructions * fix: dist task exception * [ISSUE #329]Missing Log4j dependency * [ISSUE #331] Fix dead links in docs (#334) fixed #331 * Doc modification #328 (#335) change vm params * Update README.md * [Issue #337] Fix Http Test Subscriber startup issue by moving the Thread.sleep into the child thread (#338) * [Issue #337] Fix HttpSubscriber startup issue * [Issue #337] test commit * [Issue #337] revert test commit Co-authored-by: j00441484 * [Issue #337] Enhance Http Demo Subscriber by using ExecutorService, CountDownLatch and PreDestroy hook (#343) * [Issue #337] Fix HttpSubscriber startup issue * [Issue #337] test commit * [Issue #337] revert test commit * [Issue #337] Enhance Http Demo Subscriber by using ExecutorService, CountDownLatch and PreDestroy hook * [Issue #337] Enhance Http Demo Subscriber by using ExecutorService, CountDownLatch and PreDestroy hook * [Issue #337] Address code review comment for Subscriber Demo App Co-authored-by: j00441484 * [ISSUE #348] Setup automated workflows for greetings (#347) * Setup automated workflows for greetings * Remove '@apache/eventmesh-committers' * Add LGTM Badges ISSUE#353 (#354) LGTM is a variant analysis platform that automatically checks code for real CVEs and vulnerabilities. Learn more at https://lgtm.com/help/lgtm/about-lgtm . Here are some alerts in our project reported by LGTM: https://lgtm.com/projects/g/apache/incubator-eventmesh/alerts/?mode=list I'd like to add LGTM badges in the README.md, it makes easier for people who want to get alerts and then contribute to EventMesh. * [ISSUE #355] Setup Github workflows for CodeQL scans (#356) * Setup CodeQL scans * disable autorun * add a step for setting up JDK * add codeql * fix step Build * fix strategy * add events: schedule & workflow_dispatch * [Issue #344] Fixing racing condition issue in SubscribeProcessor and UnSubscribeProcessor (#345) * [Issue #337] Fix HttpSubscriber startup issue * [Issue #337] test commit * [Issue #337] revert test commit * [Issue #337] Enhance Http Demo Subscriber by using ExecutorService, CountDownLatch and PreDestroy hook * [Issue #337] Enhance Http Demo Subscriber by using ExecutorService, CountDownLatch and PreDestroy hook * [Issue #344] Fixing racing condition issue in SubscribeProcessor and UnSubscribeProcessor * [Issue #344] Fix import statements * [Issue #337] Address code review comment for Subscriber Demo App * [Issue #344] Enhance client registration logic in SubscribeProcessor and UnsubscriberProcessor * [Issue #344] Minor code clean up in SubscribeProcessor and UnsubscriberProcessor * [Issue #344] Fix NullPointerException in ConsumerManager occurs during subscribe/unsunscribe iteration testing * [Issue #344] Fix bugs in subscribe/unsunscribe code path * [Issue #344] use client.pid instead of client.ip for client comparasion in UnSubscribeProcessor Co-authored-by: j00441484 * update eventmesh-runtime.png (#358) * update eventmesh-runtime.png * [Issue #333] Support multiple load balance strategy in sdk (#342) * Support multiple load balance strategy in sdk #333 * Fix ut * add log * update eventmesh-panels.png (#362) * [ISSUE #325]Update gradle configuration for publishing package to maven repository * update build.gradle * update build.gradle and gradle.properties * update build.gradle and gradle.properties for publish to maven repository * * update gradle version for instructions * fix: dist task exception * [ISSUE #329]Missing Log4j dependency * update eventmesh-runtime.png * update eventmesh-panels.png * update eventmesh-panels.png (#363) * [ISSUE #325]Update gradle configuration for publishing package to maven repository * update build.gradle * update build.gradle and gradle.properties * update build.gradle and gradle.properties for publish to maven repository * * update gradle version for instructions * fix: dist task exception * [ISSUE #329]Missing Log4j dependency * update eventmesh-runtime.png * update eventmesh-panels.png * Migrate CI to Github Actions and enable coverage report (#365) * add: requirements for lightweight EventMesh SDK with CloudEvents (#370) This commit only includes a brief introduction and requirements. Design details can be followed up in a later commit. Signed-off-by: Yuzhou Mao * Add files via upload * Update README.md * [Issue #368] Fix Racing condition and memory leak issue in EventMesh SDK LiteConsumer and LiteProducer (#369) * [Issue #337] Fix HttpSubscriber startup issue * [Issue #337] test commit * [Issue #337] revert test commit * [Issue #337] Enhance Http Demo Subscriber by using ExecutorService, CountDownLatch and PreDestroy hook * [Issue #337] Enhance Http Demo Subscriber by using ExecutorService, CountDownLatch and PreDestroy hook * [Issue #337] Address code review comment for Subscriber Demo App * [Issue #368] Fix Racing condition and memory leak issue in EventMesh SDK LiteConsumer and LiteProducer * [Issue #368] fix build issue * [Issue #368] use try with resource statement for HttpClient * [Issue #368] fix TLS1.1 and use TLS1.2 in HttpClient Co-authored-by: j00441484 * [ISSUE #350]optimize flow control in downstreaming msg (#352) * modify:optimize flow control in downstreaming msg * modify:optimize stategy of selecting session in downstream msg * modify:optimize msg downstream,msg store in session * modify:fix bug:not a @Sharable handler * [ISSUE #380] Remove gitee-mirror.yml from Github workflows (#381) * Update README.md * [ISSUE #310] add github action for check license (#313) * add github action for check license * fix syntax and name ci for Check license * fix github action branch typo * [ISSUE #310] Enable Github Actions for license check and fix license headers (#377) * add github action for check license * fix syntax and name ci for Check license * fix github action branch typo * enable github actions for license check * add necessary headers * update badges Co-authored-by: Lan Liang * [Issue #382] Fix java.lang.NumberFormatException when parsing Long (#383) * [Issue #337] Fix HttpSubscriber startup issue * [Issue #337] test commit * [Issue #337] revert test commit * [Issue #337] Enhance Http Demo Subscriber by using ExecutorService, CountDownLatch and PreDestroy hook * [Issue #337] Enhance Http Demo Subscriber by using ExecutorService, CountDownLatch and PreDestroy hook * [Issue #337] Address code review comment for Subscriber Demo App * [Issue #368] Fix Racing condition and memory leak issue in EventMesh SDK LiteConsumer and LiteProducer * [Issue #368] fix build issue * [Issue #368] use try with resource statement for HttpClient * [Issue #368] fix TLS1.1 and use TLS1.2 in HttpClient * [Issue #382] Fix java.lang.NumberFormatException when parsing Long * [Issue #382] Fix java.lang.NumberFormatException when parsing Integer Co-authored-by: j00441484 * [ISSUE #378] downstream broadcast msg asynchronously (#379) * modify:optimize flow control in downstreaming msg * modify:optimize stategy of selecting session in downstream msg * modify:optimize msg downstream,msg store in session * modify:fix bug:not a @Sharable handler * modify:downstream broadcast msg asynchronously closed #378 * [ISSUE #359] Split handler from controller (#359) (#360) * [ISSUE #359] Split handler from controller (#359) * add license header * add ut * [ISSUE #384] RedirectClientByIpPortHandlerTest.java doesn't have the Apache license header (#385) close #384 * Update README.md * Update README.zh-CN.md * Update README.zh-CN.md * Update README.zh-CN.md * [Issue #386] fixing ConsumerGroup Queue Consumer Offset not synced up issue (#387) * [Issue #337] Fix HttpSubscriber startup issue * [Issue #337] test commit * [Issue #337] revert test commit * [Issue #337] Enhance Http Demo Subscriber by using ExecutorService, CountDownLatch and PreDestroy hook * [Issue #337] Enhance Http Demo Subscriber by using ExecutorService, CountDownLatch and PreDestroy hook * [Issue #337] Address code review comment for Subscriber Demo App * [Issue #386] fixing ConsumerGroup Queuen Consumer Offset not synced up issue * [Issue #386] adding license header to new file * [Issue #386] Fix license header missing issue Co-authored-by: j00441484 * [ISSUE #366 ] remove custom-format topic concept (#388) * remove custom-format topic concept * remove custom-format topic concept * remove custom-format topic concept * remove custom-format topic concept * remove custom-format topic concept * remove custom-format topic concept * remove custom-format topic concept * [ISSUE #366] remove custom concept [dcn&®ion] (#390) * remove custom concept{dcn} * remove custom concept{dcn} * remove custom concept{dcn} * remove custom concept{dcn} * remove custom concept{dcn} * remove custom concept{dcn} * remove custom concept{dcn} close #366 * [ISSUE #391] Optimize interface design in eventmesh-connector-api (#392) * modify:optimize flow control in downstreaming msg * modify:optimize stategy of selecting session in downstream msg * modify:optimize msg downstream,msg store in session * modify:fix bug:not a @Sharable handler * modify:downstream broadcast msg asynchronously * modify:remove unneccessary interface in eventmesh-connector-api * modify:fix conflict * modify:add license in EventMeshAction close #391 * miss group name set for userAgent (#395) * remove custom concept{dcn} * remove custom concept{dcn} * remove custom concept{dcn} * remove custom concept{dcn} * remove custom concept{dcn} * remove custom concept{dcn} * remove custom concept{dcn} * bugfix : miss group set * bugfix : miss group set * [ISSUE #393]:perf topic name in test file (#394) close #393 * support unsubscribe topics while delconsumer in http mode (#396) * [ISSUE #325]Update gradle configuration for publishing package to maven repository * update build.gradle * update build.gradle and gradle.properties * update build.gradle and gradle.properties for publish to maven repository * * update gradle version for instructions * fix: dist task exception * [ISSUE #329]Missing Log4j dependency * update eventmesh-runtime.png * support unsubscribe topics while delconsumer in http mode * [ISSUE #397]Remove subscription session failed error (#398) * [ISSUE #325]Update gradle configuration for publishing package to maven repository * update build.gradle * update build.gradle and gradle.properties * update build.gradle and gradle.properties for publish to maven repository * * update gradle version for instructions * fix: dist task exception * [ISSUE #329]Missing Log4j dependency * update eventmesh-runtime.png * support unsubscribe topics while delconsumer in http mode * [ISSUE #397]Remove subscription session failed error * [ISSUE #397]Remove subscription session failed error close #397 Co-authored-by: jonyang(杨军) Co-authored-by: MajorHe1 Co-authored-by: mike_xwm Co-authored-by: Eason Chen Co-authored-by: Heng Du Co-authored-by: Udesh Liyanaarachchi <> Co-authored-by: keranbingaa <397294722@qq.com> Co-authored-by: sunxi Co-authored-by: sanchen Co-authored-by: surilli(李慧敏) Co-authored-by: Lan Liang Co-authored-by: zhangxiaopengmm Co-authored-by: nanoxiong Co-authored-by: chenyi19851209 <409696597@qq.com> Co-authored-by: yangjun Co-authored-by: Steve Yurong Su Co-authored-by: von gosling Co-authored-by: jonyangx Co-authored-by: ruanwenjun <861923274@qq.com> Co-authored-by: jinrongluo Co-authored-by: j00441484 Co-authored-by: Yuzhou Mao Co-authored-by: lrhkobe <34571087+lrhkobe@users.noreply.github.com> Co-authored-by: Steve Yurong Su Co-authored-by: Lan * [ISSUE #411] Enable CI workflows running on [0-9]+.[0-9]+.[0-9]+** branches (#413) * [Issues #405]code polish and fix typo (#404) * code polish and fix typo * merge remote 1.3.0 * [ISSUE #374] Add unit test class. (#402) * [ISSUE #374] add unit test for LiteMessage class. * [ISSUE #374] add unit test for HttpCommand class. * [ISSUE #374] add unit test for httpResponse method with REQ cmd type. * [ISSUE-#374] Add unit test class. (#414) * [ISSUE #374] add unit test for CommonConfiguration class. * [ISSUE #374] add unit test for ConfigurationWraper class. * [ISSUE #374] add unit test for Weight class. * [ISSUE #374] add unit test for CommonConfiguration class. * [ISSUE #367]Enhance SPI plugins (#419) * [ISSUE #374] add unit test for http protocol header client class. (#420) * fix typo (#423) * [ISSUE #418]Refactor the plugin load code (#421) * [ISSUE #418]Refactor the plugin load code * fix ut * [ISSUE #405]modify the doc (#424) * modify the doc * modify the doc * Merege 1.3.0 to develop. * Merege 1.3.0 to develop. * Merge 1.3.0 to develop, fix check style problem. Co-authored-by: wqliang Co-authored-by: jonyang(杨军) Co-authored-by: MajorHe1 Co-authored-by: mike_xwm Co-authored-by: Eason Chen Co-authored-by: Heng Du Co-authored-by: keranbingaa <397294722@qq.com> Co-authored-by: sunxi Co-authored-by: sanchen Co-authored-by: surilli(李慧敏) Co-authored-by: Lan Liang Co-authored-by: zhangxiaopengmm Co-authored-by: nanoxiong Co-authored-by: chenyi19851209 <409696597@qq.com> Co-authored-by: yangjun Co-authored-by: Steve Yurong Su Co-authored-by: von gosling Co-authored-by: jonyangx Co-authored-by: ruanwenjun <861923274@qq.com> Co-authored-by: jinrongluo Co-authored-by: j00441484 Co-authored-by: Yuzhou Mao Co-authored-by: lrhkobe <34571087+lrhkobe@users.noreply.github.com> Co-authored-by: Steve Yurong Su Co-authored-by: Lan Co-authored-by: YuDong Tang <583125614@qq.com> --- .../eventmesh-runtime-quickstart.zh-CN.md | 34 ++--- .../eventmesh-runtime-quickstart.md | 36 +++--- docs/images/project-structure.png | Bin 63401 -> 54777 bytes eventmesh-common/gradle.properties | 2 +- .../apache/eventmesh/common/ThreadUtil.java | 2 +- .../common/config/CommonConfiguration.java | 105 ++-------------- .../RandomLoadBalanceSelector.java | 2 +- .../WeightRoundRobinLoadBalanceSelector.java | 2 +- .../eventmesh/common/LiteMessageTest.java | 74 +++++++++++ .../common/command/HttpCommandTest.java | 99 +++++++++++++++ .../config/CommonConfigurationTest.java | 42 +++++++ .../config/ConfigurationWraperTest.java | 39 ++++++ .../common/loadbalance/WeightTest.java | 44 +++++++ .../http/body/BaseResponseBodyTest.java | 39 ++++++ .../http/header/BaseRequestHeaderTest.java | 40 ++++++ .../http/header/BaseResponseHeaderTest.java | 34 +++++ .../client/AbstractRequestHeaderTest.java | 38 ++++++ .../client/AbstractResponseHeaderTest.java | 40 ++++++ .../client/HeartbeatRequestHeaderTest.java | 32 +++++ .../client/HeartbeatResponseHeaderTest.java | 34 +++++ .../header/client/RegRequestHeaderTest.java | 31 +++++ .../header/client/RegResponseHeaderTest.java | 30 +++++ .../client/SubscribeRequestHeaderTest.java | 31 +++++ .../client/SubscribeResponseHeaderTest.java | 31 +++++ .../header/client/UnRegRequestHeaderTest.java | 32 +++++ .../client/UnRegResponseHeaderTest.java | 30 +++++ .../client/UnSubscribeRequestHeaderTest.java | 31 +++++ .../client/UnSubscribeResponseHeaderTest.java | 31 +++++ .../test/resources/configuration.properties | 24 ++++ eventmesh-connector-api/build.gradle | 4 +- eventmesh-connector-api/gradle.properties | 2 +- .../api/consumer/MeshMQPushConsumer.java | 2 + .../api/producer/MeshMQProducer.java | 2 + .../rocketmq/config/ClientConfiguration.java | 16 +-- .../connector/rocketmq/utils/OMSUtil.java | 6 + ....eventmesh.api.consumer.MeshMQPushConsumer | 16 +++ ...ache.eventmesh.api.producer.MeshMQProducer | 16 +++ eventmesh-runtime/build.gradle | 4 +- eventmesh-runtime/conf/eventmesh.properties | 5 +- .../EventMeshHTTPConfiguration.java | 20 +-- .../core/plugin/MQConsumerWrapper.java | 8 ++ .../core/plugin/MQProducerWrapper.java | 10 +- .../runtime/core/plugin/PluginFactory.java | 39 ++++++ .../http/consumer/EventMeshConsumer.java | 6 +- .../http/producer/EventMeshProducer.java | 4 +- .../tcp/client/group/ClientGroupWrapper.java | 12 +- .../client/http/consumer/LiteConsumer.java | 7 +- .../client/tcp/common/EventMeshCommon.java | 2 +- .../client/tcp/common/TcpClient.java | 6 +- .../client/tcp/impl/SimplePubClientImpl.java | 11 +- .../client/tcp/impl/SimpleSubClientImpl.java | 8 +- eventmesh-spi/build.gradle | 16 +++ eventmesh-spi/gradle.properties | 20 +++ .../spi/EventMeshExtensionFactory.java | 38 ++++++ .../spi/EventMeshExtensionLoader.java | 118 ++++++++++++++++++ .../apache/eventmesh/spi/EventMeshSPI.java | 35 ++++++ .../eventmesh/spi/ExtensionException.java | 33 +++++ .../spi/EventMeshExtensionFactoryTest.java | 29 +++++ .../org/apache/eventmesh/spi/ExtensionA.java | 26 ++++ .../apache/eventmesh/spi/TestExtension.java | 24 ++++ .../org.apache.eventmesh.spi.TestExtension | 17 +++ .../http/demo/SyncRequestInstance.java | 11 +- settings.gradle | 9 +- 63 files changed, 1397 insertions(+), 194 deletions(-) create mode 100644 eventmesh-common/src/test/java/org/apache/eventmesh/common/LiteMessageTest.java create mode 100644 eventmesh-common/src/test/java/org/apache/eventmesh/common/command/HttpCommandTest.java create mode 100644 eventmesh-common/src/test/java/org/apache/eventmesh/common/config/CommonConfigurationTest.java create mode 100644 eventmesh-common/src/test/java/org/apache/eventmesh/common/config/ConfigurationWraperTest.java create mode 100644 eventmesh-common/src/test/java/org/apache/eventmesh/common/loadbalance/WeightTest.java create mode 100644 eventmesh-common/src/test/java/org/apache/eventmesh/common/protocol/http/body/BaseResponseBodyTest.java create mode 100644 eventmesh-common/src/test/java/org/apache/eventmesh/common/protocol/http/header/BaseRequestHeaderTest.java create mode 100644 eventmesh-common/src/test/java/org/apache/eventmesh/common/protocol/http/header/BaseResponseHeaderTest.java create mode 100644 eventmesh-common/src/test/java/org/apache/eventmesh/common/protocol/http/header/client/AbstractRequestHeaderTest.java create mode 100644 eventmesh-common/src/test/java/org/apache/eventmesh/common/protocol/http/header/client/AbstractResponseHeaderTest.java create mode 100644 eventmesh-common/src/test/java/org/apache/eventmesh/common/protocol/http/header/client/HeartbeatRequestHeaderTest.java create mode 100644 eventmesh-common/src/test/java/org/apache/eventmesh/common/protocol/http/header/client/HeartbeatResponseHeaderTest.java create mode 100644 eventmesh-common/src/test/java/org/apache/eventmesh/common/protocol/http/header/client/RegRequestHeaderTest.java create mode 100644 eventmesh-common/src/test/java/org/apache/eventmesh/common/protocol/http/header/client/RegResponseHeaderTest.java create mode 100644 eventmesh-common/src/test/java/org/apache/eventmesh/common/protocol/http/header/client/SubscribeRequestHeaderTest.java create mode 100644 eventmesh-common/src/test/java/org/apache/eventmesh/common/protocol/http/header/client/SubscribeResponseHeaderTest.java create mode 100644 eventmesh-common/src/test/java/org/apache/eventmesh/common/protocol/http/header/client/UnRegRequestHeaderTest.java create mode 100644 eventmesh-common/src/test/java/org/apache/eventmesh/common/protocol/http/header/client/UnRegResponseHeaderTest.java create mode 100644 eventmesh-common/src/test/java/org/apache/eventmesh/common/protocol/http/header/client/UnSubscribeRequestHeaderTest.java create mode 100644 eventmesh-common/src/test/java/org/apache/eventmesh/common/protocol/http/header/client/UnSubscribeResponseHeaderTest.java create mode 100644 eventmesh-common/src/test/resources/configuration.properties create mode 100644 eventmesh-connector-rocketmq/src/main/resources/META-INF/eventmesh/org.apache.eventmesh.api.consumer.MeshMQPushConsumer create mode 100644 eventmesh-connector-rocketmq/src/main/resources/META-INF/eventmesh/org.apache.eventmesh.api.producer.MeshMQProducer create mode 100644 eventmesh-runtime/src/main/java/org/apache/eventmesh/runtime/core/plugin/PluginFactory.java create mode 100644 eventmesh-spi/build.gradle create mode 100644 eventmesh-spi/gradle.properties create mode 100644 eventmesh-spi/src/main/java/org/apache/eventmesh/spi/EventMeshExtensionFactory.java create mode 100644 eventmesh-spi/src/main/java/org/apache/eventmesh/spi/EventMeshExtensionLoader.java create mode 100644 eventmesh-spi/src/main/java/org/apache/eventmesh/spi/EventMeshSPI.java create mode 100644 eventmesh-spi/src/main/java/org/apache/eventmesh/spi/ExtensionException.java create mode 100644 eventmesh-spi/src/test/java/org/apache/eventmesh/spi/EventMeshExtensionFactoryTest.java create mode 100644 eventmesh-spi/src/test/java/org/apache/eventmesh/spi/ExtensionA.java create mode 100644 eventmesh-spi/src/test/java/org/apache/eventmesh/spi/TestExtension.java create mode 100644 eventmesh-spi/src/test/resources/META-INF/eventmesh/org.apache.eventmesh.spi.TestExtension diff --git a/docs/cn/instructions/eventmesh-runtime-quickstart.zh-CN.md b/docs/cn/instructions/eventmesh-runtime-quickstart.zh-CN.md index adc9703796..6ae41fa431 100644 --- a/docs/cn/instructions/eventmesh-runtime-quickstart.zh-CN.md +++ b/docs/cn/instructions/eventmesh-runtime-quickstart.zh-CN.md @@ -44,7 +44,7 @@ sh start.sh ### 2.1 依赖 -同上述步骤 1.1 +同上述步骤 1.1,但是只能在JDK 1.8下构建 ### 2.2 下载源码 @@ -62,32 +62,32 @@ sh start.sh - eventmesh-runtime : eventmesh运行时模块 - eventmesh-sdk-java : eventmesh java客户端sdk - eventmesh-starter : eventmesh本地启动运行项目入口 +- eventmesh-spi : eventmesh SPI加载模块 -> 注:插件模块遵循java spi机制,需要在对应模块中的/main/resources/META-INF/services 下配置相关接口与实现类的映射文件 +> 注:插件模块遵循eventmesh定义的spi机制,需要在对应模块中的/main/resources/META-INF/eventmesh 下配置相关接口与实现类的映射文件 -**2.3.2 配置VM启动参数** +**2.3.2 配置插件** -```java --Dlog4j.configurationFile=eventmesh-runtime/conf/log4j2.xml --Deventmesh.log.home=eventmesh-runtime/logs --Deventmesh.home=eventmesh-runtime --DconfPath=eventmesh-runtime/conf -``` -> 注:如果操作系统为Windows, 可能需要将文件分隔符换成\ +在`eventMesh.properties`配置文件通过声明式的方式来指定项目启动后需要加载的插件 -**2.3.3 配置build.gradle文件** +修改`confPath`目录下面的`eventMesh.properties`文件 -通过修改dependencies,compile project 项来指定项目启动后加载的插件 +加载**RocketMQ Connector**插件配置: -修改`eventmesh-starter`模块下面的`build.gradle`文件 +```java +#connector plugin +eventMesh.connector.plugin.type=rocketmq +``` -加载**RocketMQ**插件配置: +**2.3.3 配置VM启动参数** ```java -dependencies { - compile project(":eventmesh-runtime"), project(":eventmesh-connector-rocketmq") -} +-Dlog4j.configurationFile=eventmesh-runtime/conf/log4j2.xml +-Deventmesh.log.home=eventmesh-runtime/logs +-Deventmesh.home=eventmesh-runtime +-DconfPath=eventmesh-runtime/conf ``` +> 注:如果操作系统为Windows, 可能需要将文件分隔符换成\ **2.3.4 启动运行** diff --git a/docs/en/instructions/eventmesh-runtime-quickstart.md b/docs/en/instructions/eventmesh-runtime-quickstart.md index 0536e5b6ee..62b315c57e 100644 --- a/docs/en/instructions/eventmesh-runtime-quickstart.md +++ b/docs/en/instructions/eventmesh-runtime-quickstart.md @@ -44,7 +44,7 @@ If you see "EventMeshTCPServer[port=10000] started....", you setup runtime succe ### 2.1 dependencies -Same with 1.1 +Same with 1.1, but it can be only compiled in JDK 1.8 ### 2.2 download sources @@ -62,33 +62,35 @@ Same with 1.2 - eventmesh-runtime : eventmesh runtime module - eventmesh-sdk-java : eventmesh java client sdk - eventmesh-starter : eventmesh project local start entry +- eventmesh-spi : eventmesh SPI load module -> ps: The loading of connector plugin follows the Java SPI mechanism, it's necessary to configure the mapping file of -related interface and implementation class under /main/resources/meta-inf/services in the corresponding module +> ps: The loading of connector plugin follows the eventmesh SPI mechanism, it's necessary to configure the mapping file of +related interface and implementation class under /main/resources/meta-inf/eventmesh in the corresponding module -**2.3.2 Configure VM Options** +**2.3.2 Configure plugin** -```java --Dlog4j.configurationFile=eventmesh-runtime/conf/log4j2.xml -Deventmesh.log.home=eventmesh-runtime/logs --Deventmesh.home=eventmesh-runtime --DconfPath=eventmesh-runtime/conf -``` -> ps: If you use Windows, you may need to replace the file separator to \ -**2.3.3 Configure build.gradle file** +Specify the connector plugin that will be loaded after the project start by declaring in `eventMesh.properties` -Specify the connector that will be loaded after the project start with updating compile project item in dependencies - -update `build.gradle` file under the `eventmesh-starter` module +Modify the `eventMesh.properties` file in the `confPath` directory load **rocketmq connector** configuration: ```java -dependencies { - compile project(":eventmesh-runtime"), project(":eventmesh-connector-rocketmq") -} +#connector plugin +eventMesh.connector.plugin.type=rocketmq +``` + +**2.3.3 Configure VM Options** + +```java +-Dlog4j.configurationFile=eventmesh-runtime/conf/log4j2.xml +-Deventmesh.log.home=eventmesh-runtime/logs +-Deventmesh.home=eventmesh-runtime +-DconfPath=eventmesh-runtime/conf ``` +> ps: If you use Windows, you may need to replace the file separator to \ **2.3.4 Run** diff --git a/docs/images/project-structure.png b/docs/images/project-structure.png index 252a9536c6d3db23d02097c719f2fdc46a8f71ed..efc5249e593a87b8c3908273e55ba13ac0768e2c 100644 GIT binary patch literal 54777 zcmb@t1z4L+)-W6j6pFjMlood=+Cs5nMT!=8C~m>6KyfM3LJP&6;O?%aI0Q|w0tFH@ z_?JGr&+fCk?|#?+z1Kf5;biV%?m6eo9G$sexL*elypmUv2cVz;0E);Tz&!-;Tn=bw z4FIUB0@win044w>(hh)zgpgeTRLBYdJoxtD&lVKhZ>WF5sI0#q-a{WSD9Os2yw+5g zS5lGtW9SD2W-c!8eh=d4EVwxt_<8yH z0D#~0{@Ytm7XF|k3K`}<+uX+h0O{|@=MMjD({BU-%3uHhPW3 zCMqk6Eh-8V-~k~DDj~{!AAlY?4m!#o<k&#nSQn5Z} zd-9ZBP)Jxr^qH9KOF4N3MWt6-+HZ7pk?2`iT3OrJ+S$8%czSsQeSE{-het$yh>A}B zn39^7{^@f@ZeD&tVNr2OX-#cieM4hYb4yQeU;n`1(D2CA^vvws{K6s_0^Qi$+TQuO zy9Yl$IXyeSKwMt^#tQ|2`Zu(YpMS&b-|!+t;`IOx4HXUJH(n?Yypa`^5DlG??;+6( z4Gc3^VkZ6&OcI%-oa*jJ%mSJ)QggRSEHV~B2rK+IYJXt%{|&Lw{}pEcKtdA~cChJvVIW)GIjy1C96FEcID~O)=`$42=^hGX3G1WBz zxj6H)n7@cUA1k8uK_%_i-BtLtaSve0R=EdMHcDSTD|Ij{kuyp0aD8KaFcuh)O}sbq zs-a5(jWquslka?Wb`J;(!u3}#RjzO_U{dvwk{DF7j&!9R>D$Y{`eJzjO6;`F1|XEX z(pzYZNvp51j1|ORNi13|zy4-&7P~N@uA;3~AfvWiFjHa7elz&t9?+vzdR26LqHx11 z1}Y6&6FQ($=)rXuG{%SZUCDJM<)IYXx1?O0tSD2fJKF%OhAya+k_E`%jn3#OEISXH z%Q`~>`4alF=wOFQu4WaWXp3Y+?4_rpi^DYN?|)e)y)KTlpcHw+fVW+u(R*OYW=Pob z?quRmCfNQ1wRY7&05I+NTBYHK=Qz1x;|%yvsHM8q&NSLV+`A?DsXNtBxrL1Od-m0) z?V3R98}V$tR#;KAr(DFwix~-3t6WtSYogX?@d{{whrQGmjI~)SyVqD`?coP-^7`-C zcSK)xXQhuar2ozpkQE{o#-sg%+h4cR)*vD^z$(XB>AXT}WVf(&`e5@@zOfrADt4+4 z_^1xH0{<8Uqc*OdJgf)a$mXmCS>1lx<26_nJYG<6+ou{9UN|SwF{K%pXq-yUi?kDG zY*|PFw@UQ>$2-4C1YAq7M)uJo`eTo-}UYD~GCc+Fs5gF{|G?;ZIdx z^7nxlz|4NI@aOW!B?Wd0Mrn#3t$x>exC7tdB56J7n7!0@&y7;ZC`j|GW{6tlUHi?c zw^U9#|C5icbqLlCJI{niKV7+6mx0{{@k@A1W@)QB5m52iN{t+=$0`p2ZjW{i0_hRh zP!obhu(4c)?YRkfo_NVlW5wuG{>pn3eE(Q3 z|J=rNr|OII{osMGd24U?sicymI)(b{)+*(30-bh$_RP7q~#=TYcYF-b5rR;T-+yne4?*Wh0 z%J#A^#yzAY5e{X*hg^@Zw(kaYr#*-A)4pG*eJ*TFk-2+Yik-`A=|n+F8`yMZJ_s4? zjdR{8>IFqOLw+9AjUGH+-MfWmtt)$Tea}9RZRV%Vw0s+v>6_@@|8(kG>ZRWu3f4lz z!FXkM)Un9F)7He+|2cv@|3%3H!tst~c%7P7S1(4j0gltsJd^lYzfZqzIp3#Mfi>d$ z&iuGzr&SQa1ai{;Jf->G#0s$l@w5hfJfM-AFnEXF5HUq|-`y&)`?C7!Ujimdg?1_Pu$5T&Df%$SXxoJ?$ zZCgdKDPegeV-#0XU93)IU&3PK^V-{Q5?l~x%$^@Wg+7#m^a~I}h{rk8rNkg>1gZQZ zCrbE&HmEF!C#b8iTKT~1Mm^>~5xxfu-UDjYV#;s4+hF9q1F9IkSAj`nC7$l$$}Uy? zjoU7?F|~Zm*h$MUbQ{CKL8{8U3cDRexP)#XMd$QkJxa-KwB=`=$yt_!UoS5URazkK zA5XjiWE~ZjVB)J;u*#*vpTzx#+%4&#zM+80MIK*$#b*}p+J2O!txK+cL7$O~ z5yaVYENl}tG%i9Tc`(AtsR|mu4N|$({r6W_Hg4#I9Czjqq-M`=Wq(bUc*nkPZ1gGU*2HHSz_nP}&7z<4r!5A4RGwy;+?uutl|CC6 z6e#Zx!Fp>cX2FI*Q67*==VUDF>|m0fnSG_e^N)Q<|L4}6SMr99>ebFH20jj4zXy9OaZ|n{>|oj64u#Oz2`5l{S~}q%|Q0# z^Ht!n5yAxfTZEE39S4S=2@7Y^Aim3NH~~Yo}j66G7s{ua(q!s{n2$T zZBWI!zN~+Y8C__o?j8_2;mw`0Okd}F59m_3|8Y|e14Y*#p*zfVu)t=Q^w$;}Ha#~9 zfb3nck=_WcZW^kXzATox8>5O9L_(jpvtO~vGaoDWVaZ_B;BPLayhvXm8jNJ6_+8dL z;F$g%V5M-wxORGR4@kTRT(e&tfqt|ff%2rJucg{693^!xzo-U;!(ujyLfi1y4W;oM zl)UC$B*q4wq<$n))H6+|O(^i+xd&i+-%8#CHl^=I5)iVTyGQo`#(Ti|8zH_rmh!Dj zTT+wu8Fgt4l7fy&_ z@p3#KexQT8o-+k7diGu03uw%(+6s+g^@=Lz5Dn5e_rXMaAn&`LZEyx_KE%r+)23?6 zHVo>LGUSE4>uIpSv9ve9nvc7vzxm~zK|WxDW0#G4;7==Av0>Z|8FyB+(T?rk+0&g| z?(6!D`c7Mh=3wX@Am3dD<~^{2-!n&#UtUnv+^UWzS7ET16!(F(VRj*GQwr*xOB86@ z$%`t=GLNKk5A8ibJ?Iku9#E5aM{soIcMli}x=jTp5Jny$NoAy7sptQyA=B5#=aQ}) z;!NvLf^dc&;qPY=SI>FAX~@{WmVsUjhnm!^EJ-$gMB{5^v?Lyt1;7KPqk9a`K8(&lGVU1fvo>8s-#5?URP!-2;j`ZzAsj ziyD9BVr0~}Gq)S$#K@=#Qd4vxw;qy9dWbUEM`b#}c?n{(G9&v{F)rHvj$B4%2M0SR z9jK>n)Z!*TDOP?+9bJCBp9xjqWDtJ6^Fu^yZwCH(iyTy0G^chNJT(FyROK6r+bRj^ z9pc5KiH(0aovbjD4T}t#Ub~aM2mC7jm({6tP<183GjS7hF8bAxe)MMI8z@l3Uwqbc zv_koH?JPPd`bL^8u2Pn&iwbP0x2|VXYkm)Cqt3ldQ~g4BTe?e6d4mGsa_%;;iFB+N zPryaUOPXc*y?<}~c6qN#g7y8YYa&5$UWu^{mXlzA`oFM){x35+NB9|Lo}B*TF^g)y z4B{~v`vFVn?~I~*Fl{+`Ll1gu=cPF^-KOCPwz7J|H6)Q(Dvfo8-xXA6om7Zm>&>Er zKP;)VBR8h73%v(e_S$fIPb%?bgk)^s;zTPMb@8$b#Lb@A{B;}tl`n5*`XB|ZH`42b z>bOzyqj021@Y9V5lwPz8B!RyvEVJ=+2Q9Cdto+oqN|v>GTp2;#%MxjZVNLmpjps}F z!p{kj*?ZAwe;ha0&;_-_S>b0Mn_eU z7*5khwP*Pqi0t)o?_PR>Hjy&fAOQSf!_{Fr=Tl-r-jh%{w+aSuz9RqYY77Y)oJ;Tj zN`(L${F}9GzofHw>c@9R<$gRLIs}0xx^UfoNjLBu+!Q{LObcpTcSXg5=C+hK z=bR=KL}S27VB~ZG4&bqUkP<@BUGy0<&eW}lZQ|qHXjhS2#VH5;va*AiY%^tlOcP2(mD9?3JBiOpnt5wpos2x?7#Y;bA zAJnoWU0U*41Q&T}o1Tsbh(py0*?JPW6P=OMxOvO?B#OrP(9ZxfxXF16%p}I22c(bi zz3#gp?_lzjw(QJesj8M)Ok&piR2S_7uISoGiX~sZo6dTWxzYKd;|W(ppc2LwI zMvH-SQ=^*d`1=MdVyZ*x>>NKWqkvg7io?z7E2Htvd%TWs1E)HX;=b{;cu5N$caW8h z-X&I^UR`j!Jf_WD&{7qW_h!f{sQq|LP0Bgpzb?R{Z0xlYj&$;+-@1H%+~hnL#>@K5D8Uvc#k70J0yA!hWy((T zfP{`nfmxCUV+u6Zd}z)zv6cSg-8}xkXwwf! zULdwM%%9x@qQ2Y%zBd_4(*ZjM_|U}iL0F|s{oIRr8WcSTN~r9K7-)ciy}|z`V{}F- zWmDx^mxg-uBoqXabviS|_53w3!aaE~mW^5`&N=LkDslYu$1biX7!vu(N58B3#8*QT z2!SCQLhY}hXH3uDq{&-p?V)T)oz?zToBoSU#M^H+-F|jzrQHK%`E6mH{a9&&H^g~E zI|)pdyIpTTf_h9{a?s$hc^o0h`4KM$ei8W2-2);#zcLvqE7wf6^-cLpIv2lHb@kE| zv1txZ`5N*8m624NNJ*&szX~svY7#0W(RMG%X`~p%o9>K`F2BwMk#J?J)ut(r%g>z; zj61$0+sBNMkHg})tVn;Z!1;jAmukz=a}9IPSL&k`^wSZUBc){=dU0$)9}i<92bmRL zqF!8=2yLzYJ>b774VgZ(xvAeWQ!Rq^2&uitRi%w4L8peegt-~3hdt*$6`|q44DqVuw}o2I~X= z_rp#0;zPX^i%{j3)!^s%-fqA6A>lrrU5v~GsY_j0I*!|g>HH5L}K3B;BC99W2w+yQYkhPcN}f^qfk z!&^-c+cJAPIAK6oLa5oynTA;agv;+B8R-+IFyiw&^ju1s?g z-SG%`BtQ2!epJzGdS|Ai+jiB1`?@f(pk)Mgn}36XADvQY#{?F=kF?n4cshps;~pwP z+Z}hW(^JQ6`MxyhD_*)u0zqD|^uA6UTZ5fAPrs$cBq1($l^HH=`K?Ok`k9WYO-nPy zgrv5T9a>-^B3%pNSpZZYY-k~mCf9F=8?6+K*FAM`B^%0X@_E%d6yhCx6aU3UNff=` z($ZfQK@D}r**6$pi}Yx=f}LlTu@6nZJi+vO>VsqUz7>`DcNDqPu~$J0!xT+C+CMu{a&S;GlcytF7;3K?vtC zWsDEE&mKMQXktKRIToyZ{!@hSak0)_B;^`9-A4uTHA{IblM>Vi%{peXS=yJg8xEI7bsgU7s-v4;?a@f_=W!sjjnUFrpQdMV z{r$sOS%T(6bgjxyZ(=hwyd8p=46vT$))8pPkpTV|6$R%W;1bl9eML}GN_mIO;`m6P zGHPiLHD%vV|H->E{75h;T!M{eRdAMKZQs|wRB%Nar}}bXnftM8u%^Hy%BaX&JcrSr z-*9P7bUNqjmD?S()4#X1rxvEd9`3j&yYOQC(D5XBjjUs*O~H$z2#(5Q2~%F~40w+EV`AJ5qIZr`F%IvwFt=T{=}jpEqAX zGV2;~rQ%SBoFThYjpQ*Y~ zk50)O)eZ;esqe^$kW)a%b>r7pZNnVMooRge;eWx@-wOc+rz-{J1xt%5N^+pt53Ksl z<$J(H+uFtMJphq`jKw5mI$s@--$igtv0J`Xzn}S&*$0BsX!gd;u=ApdpVM;guK9^4 z3nkKa*HX{E>QY#0^*XY|=cA0fdhV}bvrgXdxZDGXguW{F-o`K(8dCentQf!L-(*Bh z38UYqi6u;>d>pKhIBAeER}7v5`INO08q{xso<^fB5;;Eu$1LE@raz}Q1}d+2hSpDD z*<}?H z#j&VddbaVn6D9|S& z^+@hV|3!3BsP>0m4yO9|k8S4^FBotiE2BxRd;rCk6e_=vBhmWb2qF{~Y50jFGSZcM z5BSmVE9cYpuisT-qV8w^T%b=X<0pDDwsRn(Yoz+m*``l-H910K%i-jdI>7E_g40!b zfdTjDG+QJZ&&hO-;UrAzOvUSfK9sBJj+KCWfHYCin$3;F&m-KMhf~NDGVboxsD14V zu3wM1C#h=6M8AuLKEIkr_k+nox$2jz40iXuvG^Xbt7DbL_hGp7{**H;96GN)^pQv& z6>v@+ZA1phZ|z#wtj7nP++*R{24FZw#2ED%@tSM+1%oP5EpiZ{jF8bL_(}*t zRkYs(fBf**PKza*p zbDcZwowujROrMDD^V-cu29TP0*7zNRbA9~4>%=nLx!NEq!FcQ zNC7hUCx6b@!V*_(y}hLgJgJeV{H_$rxT5LI2ZcWXcQ%)!;=7;2}HyS@sc6EXv7Qmysu=(tZ%d#9D1`arFY= z#(*zniP)0-4b%tn-MNJ)(ZD zucgZ0x%hsQ@}>Tzcj|`de)Q-!eD6sq)P1qTNSlf!doOs*f{vwv?uN?{TI~GS)f;hm zf826|o2_?p9jA8NYb6%P_`SV+f&jUIe%SuqOI3`!He_z&hn(@ujVfJsNgNZc)ZyJ` z4;vF$$fa1Xn2oc&-%A!}d&a!-JJ!Oi>N9OS*4dv4-z#L5XX-xRjh2E{t@vIqJ&wzB zEXwHxxDFcR885;o4laz@>Sqr<_A@cLx%xD23mymvzD(~zZ=#+*0aXZgqWIJQsB^{v z>b|X;Do8Mx(%JUO54H1D+DhYJb*!r{Lx1yZqnKhhq&2Jksn*B$)9Pb;J@etV>?xZW z0a`8B6XRDLNA!_vc>a_d75Lr0b-tcC>Nxi44ZbrmSl^l=Fjw{Ro8CU?dLK)>vb@vo z>5CT*i>Jr4HGa}Ib^5Xq9(OiV*_c+N2*nr7_d0hVN}|qnr2B(0k*mO8rZrgK5$PGWY4GQz8*Q@J z;_wa}eTqISuJD&$mnQI6pKgg){vpA-ZWPpI5EuiI#{W4F&a=I< zT1m^JN%L)8cBI~kSTA9fwQ(Xh6TR$$#+z5fo#dn$qmL+(vs{GxyH*Bc^`)d zGtMuBa^Q8o65Q{^T6^oy2$TMBBVFooK8~Kljv8FA zrt$inxlH0X4(?VOGbgbh08joH z<(D^EP@CH`=x5}FANUI)%JLE3dQGtY_-M}h(K6wPS+9UnYFkzhB|W#z6wSWPMCw9S zn(m=Mmq`=gt}lJ)9n(((-573vS}REFSAqUCU$aomP&rRW7C7@DKVhUPJ?>%AOmzBy zfkLpvi<}!>*x^@*I4)ff`lrJBwuUGjZ)W0I|L}$hNrF&AcvQUfT9C44Px#5m#4#wRvh1 zpG_(u4ME)ShgDQpe($wt~4ykIZJgX zSbtVrWc&n1Hi&wJ6&LQqAGE$8UQ?L8Hy68+8)K<)5fGQnGBY32n{j;wBjHSb4)y z2gt9`k0N+HA7`u1I2&p-#Z6M?BVDw_A>FZIfNLw=>{K_r7n<4_l@Iq9+jv~Y)nXu# ziz(cO=~g;<8K~d=(`#q7EjL;RJL|(efb=GaX&39NP(I9?>n!n@f$mM;6r~YDJ(If+ zQj_zZ{jy?NDK!NTT*_kg?*Gcgd7<@zAPzdp6n`zeeiZ zCVEHOI-j51qZs&obCCQCY=h2Fg?x;e)@||Gv&UZ@;cL)}?8uM3WLNtIDK594sYgD1 zPqc7Ot0g%B3|{L~PzY@bYE~I#Mbh`1(90J8q$>9<SlVRM>BIfVX>!eH5?`~Xt^xMO_fmrX%CN_Y zSL6{wY4z3CHhLLP3J7ZhHt0W<>q;%VsqwoT8_IFu?KrUYsZ);UEF;_kRJW?yI_{=E z(=vPcbzo#zTJhU#(Krp(JG}KbMZ|fQ*@=`iMpBy`wUeuXO!g4Hq| z^X)QP!CM=t1oa~XnG~O$obro=yCgg?}QIR6QU|lN5?G;?MxJ! z;tv-%d2_k?6gZffzUi9!T!zpByM-dwh}&F_piikaM}bu+CE1VZPlKBWVgA{`+wuBt zjaS_+xzSHQ{NXB;HHbOom9$mwHq@-t5!Ov%YUDqq3fM9I22pSWlD-N?#*zM`GG-s* zUwlMbfuusR(#!NmY?gd9+rWlHLg6ZYqBEd+hK;grg$Qk~2@qG6O;kHSl$-#`1}>pjaO zFw`NkM+R<;$(?!U-QH@Di($LUt4|q&()Ltz=w^TSfciWDVd41AHbXaxS+0GyZqn1W zw4<;}vTiEJk`SDR&4fAt6~}Aa7?ifX4U(CchS1&h%~aImGam%nEN&}#IURc)E~p8F z#h!InI={NjkaEFaJfJ^i3{Sh z#{1^vhK>?}ya)mP4HrSUklpb!6SX-yZ|@V>z#2@0x-77`ZYY;RoQOP5&tm1xH9ojgm5xb7KCE1BB7Kss&Unah29cZDc~8 z>WjI){#Qq1;ubXP<)Nj?4=J==ytHK-T*}bSsCsA2y+GWK_{9THN7Z`$0>YXxHmf(_ zRz0S{a0;U@zVnvZb>4AYSz!1Qt;(xP z*NHS1Q2fbSV4WdCU)_7tiI)-6uiI+nl!sHz>Ftu3cp@l|XV~Gf$7-tDaOWQ9@QXk~ zcz9!2-F^(MLpM0CKB={-*^X>;f`7pKu@`&XAO!&SNFoC^i4EoZ`SXJ8&LG3H}$h(&IP9rAH5rwuU<&0LX2I%R2dQS`Bg7A0p7ROuLoy%emp=avdgYRX4vA`sU5i3lvFiWr+@M7 zh0esx2FF^sm=-i)u=*aqV9C%y&QcW2)~ik?2g~g45T2^!IjT><>q?s*6qlo|ie6Sq zE`DKcj=95N>N*TcpZIkTfGU4w)`HVUloVEkzfUh^&e|*(1UJU&)rN~2swtB+Nz=0MjJq5rIeJHd6k zOouIZn=j#QMD3M#h#C^i-;Obcs{!*yh9t>>vaL6C*RfIy2^brq8oLEfgsYoAZ%c!A-pi%9Ta4S zc)#${ya{Nm?86xGQWxCqmMfzKAd=_DKy}te@WC>Bz}bD0vbxi3?Nc7qs{B{qF%Mak z2%TQGLf2Xo4=5<#rD_dI%#Zpu_D_&GbdbzSu$K9d6YNuco^|4;qjPivS1_IwYJbqH_Jg?hfU&;5s_}UJ_Ge${b0wZ8B79~_ z(eCXS-WG8_Zy55NgzgV#q(8Z|^z7iwsv)b#t{|DZ%M8qZ*wtXEZ7FJq}|OOsiti!^rih6 z`#Jo?6tO}saWbg(;shHMVN8q2gks+cK)Bkd&>JJG(Wmzs%c=I{y|I}rMQPaBc`@kf zKe8QAZm`11!Yl2-UYTZ&kZRGI>2Dz$wX&|%^1p^EDH`TPdja@PYX0n3hsxJWF}uO~ z+?GBRJkQ@IyMBR>3x3K}n5V}I#Ix1pE_2+#$5jH2+mzgffXH7fbBPJPeaNTO&XS>$ z>_UuYYDu$Wzl4;jJ!|+5L8J;FesO!!ig&M>X3Y|LE6Kbam=^TWM41@L{6qwpFm~vX zpoBN}1>(R)5So$#sk}tg6z#=0d?r@3yb`CDh`}O|XW8yEDIhp_j83oZJs2fnkLHDm0-4QgO7islcV* z*DHF>fovAX*C}&+BuMGTreh<)#b&aj+Mjkt<(h{b)9@DPc*6Ouf*if_m4e*7OET^nYFu8>)o9^mu!y$&U%x zoTvKU?=e8#EO4?H(}yMfwUefA$mWK;aWJ5RCrhEk`_OxBBU$Dl-p7K;|^da z#Z(8Qoy+$2=v+#l7p7|AlW{Dp!}-p{C_2)&>9leqb+%$lUyC1BZ>Z==$$gd?V`O{| z#m1q1Vv5jOPwz1@>0KqB7mBla(>N^&-aKm@9W0< z>L*?|(IRJ~biC}5F;b{N_R*d|=2lo>wO=t()V7CyKYlhz^oY)fjF$=EM8P#vJ&Y0m zqG*h!Xt+*y>FVNFUcoMytjM`&WzWr_MKyAX3aV&9W!3kgHH+fI?e<|W8_h*8%0%O@ z>-8n+$d;fe=OfRkN>5x~liP;xE8KNIEv=TBl9+YASV%NtceBK&G$*>%ZenqqVjV%U zd+BMAfgl`z$;#DbJA>~a2tsP4;q?HKf+CqhvNyY%nkWs?i{VY(QH*u(zGjc>184O~ z@R3rLUx7nGGOK2T6MUpwzt@z+X{Bjgu)FX0702EnLI_sW+u{|KkGKc)ll!Vic^-(` z6IR7Z^*XVj4b*HYe@xVkpkz258V`DOLxJ$mqJn)dfs-4y*LJ|aPA~0F>g5XBllGZy z+32t;lRl$>q%Vg;M*JJ)0^IStOi+L}y;tXra1Mix1|w`kN4|_b~Xw`cZvUx}PIrQ}NPKX^{a6#iy;DJ2$EJm9KfZ&I@< zfGF!p^Q9d>%SwV0K5H_P7bH*1wx<5Xo3P0`_$Xdrf+^mTrW~;veWiax2_rpI#XIV~ zW9sav4N%cHUVO`GzWXDT$kwG)qA=jw&ew{C=u>*q8)D=wB!7dsQ%N$v3Va3FY?#SD z#Ty{>O|*zy1DiYTNdKn}mlvq@Qpi}^liZH3DQO#IC z(i7DkNNP|L!+8hzrH-5)4KYu6CWM`@vdkR{CWI;lK;>MX8Le7O~+bsdoY2-Ik)) zJ&uedBY)k()(((&>V&chOqLL!zH`+8>nIXy@RLQO4gDGsvYtej=d5ac18Qc+wHwhK zdolQ-Yj`(&kOSt|i1x}=J8$sq?09>Peknzcq0KQ^>GN#-@_I5~aBqCR_ZqGgp#g9Q zBHsw%V%i2Djlh)>t~ChV-sMIU89ln}Fq;&ZIT=u;>B{vNkPFaSPYBJzf^SztomhUBUM~_s>(I($!niIJMNZ~GZu)T>_$uaF6q>Z^xljhPbAbft@8H5j1e>*4iRc9H|a(wuGO0LYA^uvHTE`ShvoWd7&!9L1QZ(1-=TFOH%hMT8^tKm=+N>wL5` zHOEp{70>X|F*92zlbM}wo)n(3Ymg_yP4?Y_kh<~1lGEDT4T@6I-L20tz4PIA3ZJ<% zJub88f<6X#KhnabJZ|<}1sB!NZwF{ytL&bWNyFl9qru|Hr0u(vt=vM_%?(kS4I#DB z^N8vENsr<-Rmhv!vkIayeZv*%MYE8N*Kcs<_Jo@?_I#c(JNoy_ac{ydckFG(Tif?1X{=$t!G`nh;i zu5E^#YFulmD7r6U7O)XQAK&7Y3gP1D$>N4B&g=FbE3v+>ULCf5p48~$R{DTN|EBJnaG-{Sh$%8}t&`<>ELSCk`DvuL|s z#`MP(H{>PiK~%TpjbD<#Pjl+?ZFxm3HKlzQ2${; zk&5zVFv?bfK%qeDm2SwST-fJqb>w}!h-nEW3!eZ#h@cc}XB}D1D)pjj6{E~$i9=in zVk3)}Uva1ip?e-Rbwi)SBF6T9ZeCYez;>$Nk6r)O*t0BU-d3E?nm(GMNP;}0Xg3H;b3jT6B?Mn?zkI?hH3L@KWOk43mG&p={J5Ktlr z#s_$#(y&GO7G$ihsmPI00ijP2vrdH^kjud%!?ROX8Rm?s?Ljk=tDI;6+2T3(E!g5`SA#A4H~>70;gd-vpr&;%O7(__>KXSC(% zVSY=-t_}5DC4pn!M@!w^JBa|$m*U*+u?ZKrcfSetp0Jm2ASTSJC{k)7uqC!uFxx&$ zDP$;Caily6^#-sJ68sFKw&gbO*A{%{PPiVHq7n2?UoyV>-GwugiTp@$K2h+?H?Q$X zHi__&9$kutCC%7X{QP-b&7@E#wn&6Odqz;C{}ZPyS_z|Ci5eq07OPb+d1g8@QLx|1 z(BMQT?Bl-9RqA<89?~M{bWSbs47E|&iUGVzU1?;atn`iMB$s^cuio?#;&{;;)2h&% zaSzbGoA$f-QtPzv;dPFt-q+@pPUPi%T=Mh8nyUIqAxXoY*Y?Kju$9l%)iW7elR8`l zD9Hr*0)POYQ373|;}v8ubRyqFJ;7Hy1Mh3T|4Uek6npl|=B&K|J2qRHwnL*d?S;Ml zrc((564DJJ`tW;z?YMJ~7}s5PuAQrak;%S|E)VL5iaBA-3VR0a!L&DgU%P^REv-A; za^tm;*c*&)946R+5E&k@qTlz-$UbD9s4LIOD}Ne(Y-^+VqY&z*>udr=jZW)551%@{ z_0~w{!F;(&KT|jHtN9)7a+bq@(rEjzjfMGY?bF6=nysd3e{YM2iKZV1(yLa>b9Ww3 z=AG-lkK7AlD5B+k?N9dLR$WcMQYpO+P<$C(edyG^?{Q7p3V2 zK}=|}AO5@w{h4OY<$HahR6;(ww8RgYA{`uN&cgXTWc(0c@WC@l8^!h?o(|*0ny=G! z_}lnPt6XUDFmdw3#;rAz_0lYoeORVmWBf$kn!Y3yq`x|eya&7>P4|PvK<09gnT@&l zLc4|5lr-C!k2B#4SkfrQ4iKKW4%|rzzJguy5)Yf&#(1_xs?_B?3=9`&O9J-ZDgmN; zgSzByg_t|3BkzUKFTI6Ld8 zxVARU7l8mlg1cKF6z;CUEeXNh9fG?D2yOv_6%agFaCdiy;83_%p@rV5JN?bQJ+r!R zch4VZ)q+~Zs&zPf?^mAZ_XZSCbQ1gV|A6WBM;2N0!EKNYeA7efzMW%ZU0Ka$zdfte zrv3m}vZ`rKyA3lgZq+VcmvQ@B!CQAR6_hh0^i3${_Py04$BW3N0n=i{QzAmkhzF?( z){Ek{xCFzjwIeH8$+OOn$5VA1%j&)A2c*KsX{1k}(gGdU{AHV2Ig0Ge`I~ib(?(YrLHr}y8_G{S2oZ$I>e>cl4q-=@yZ;tayM?a-$7<{ zbq2LkB0c3^p;IAQuaiJE)|-o053p!-c+zRneeoK$$k$Df;KC9QlC|WhOM^)#>BOl( ztNNy?YXheNF~&y7oDW;Yn`GC$9t(+s66t&tgO&6yM8x<^0dQ+O(TyFX0={}nO3U zyynNsoegPuQrh~AU)JOI+f&12_XjC2M0IG@SEp-M;g3Q@U*RgbbievA$9xNn@{uB` z?=%yyPeS9~3WrpWdRg7L%!o+-IPlq7=WDDE?bG7tYf!w2(Y2vY2ge&e=x>Q#D1cysrr%LMBR|$A9(eE>6dVFYa+iqg6ac3}dz~ zFhZBNupxKai(Rv+Ej^odjiD_Klf{Jd4MryFD(Z(;0fD_7Z)j`pc#)rcNY6Ty?NP5} zW&Jldy{{?u7{k-;`74yTC}OL2QE4d$yBm{vo(i%L*7Y#QHt7N#^oI&whaC;=AdGId zSHI8lVG@(7>BzNKPdTi0=cvE!?$4tg+}fqpf02f@*WFyO)&hfuN!ljA(F;- z7_~{?q$)kcGnl7Lj=cyHoD|$gjJo3rM1V~Yw4}Q!EuEgMnUeF3xK%~>tD;sA=$wMN$~hB4Eo{?n^x9oX*JvwShp zwUQ&Gnf2BjWvmO8+z+<1W315}XchSbmR#{D{Ji#YneW?jdh-KkU$7JvyH)z&#=}ay zYRb@~8Z5fNYhtkbz4g?pG<$emrZ36ZQB(yFb8ellK}|C`Ug)WjhF=kl*K0x{`IYm);5QI|$6{vLGpo)?`6F z+UhjowWCpH6laJRF<^u#^6n;WV5Pf7@%K7Y9P5Q5>oQA^$ST3AYbvwzO1;TZC;lTQ zm_3}dF@!f-R7!Y$zOvpxNE^ve$2vQp&XL$)F6?#%&`))r{?d`syhs5ozpC0Ga5-UGYg{2E(I;zg%N;WBEaaXzb%_r<^# z?hwf{toOcA_qE+db&L@#k7NZfr@l0y^>n^a`Xc@rLk7Jeidz;}C+bp?0j*y{_E}n^ z&+LnxTZ~Y#VlaSSqa(ZE15dcl3rc)pkWSEm3a^Vn3;EiArQ`Vc2jE%M2=Z*&e01RS z2Vi^t4*=F45LB92&L{rwq_0(q5b6=G&lVl%Y0@Ir}M|NGrdY z#~M6?BN^038>*81N1u{W{`15i0Bra67qj~HcEtO0tQlvsYNacvsKhPF1Wap{!VB??9Q6<2$N;b4x!MiXr_7JTC*Z3ySMM*ziB+I87+4*nsC zZlS!cYy(TC8Grcu4l8k@xrcEfHFc1AcQ*!Sq8t~zpj0p8j|qxPYYv@Oo#d;qfDL^jAvg84B?>7DA*I~389g(a1=^-=;%7FP zfQUv;gm~>AfM^lBVjB>1N`*t6?`UBx9VqY(_=v|(VTWzzRhKcBBt7{DH}Y}_DG=TV zBzm6o!G+)TMuY({r*Mr<|Kh+R8RB_^slCTk}w2E$l9WXK8Xy zU%D{FanW6TT?&~HGu`-#_;Q^QZs9ZAa$tY3JbpiOc1qI{HFJ3C7}IH%WQo?{^_wq9 z+lqvaE{cs%ge~f37!Gh*U+fd3g`Cc%ejeV+qzQBvmVcI(8X@<#$8+T*0T7UvQt4$( zo}6xvEu$lVBJd#pAC3H8zZzEbBUn_>|=LoTk*aBWdzwEj@yuLqIpEkQNXd7nBO~KHKMPna3Q9aFP zK|LHe59;89>7IA3Q_j|R;`DEw8cC#DHrvH=pPstJGk&K;L0i}Vvdvg|l7tFa?3vR_ z8}V%$yHD=_1AyPg38o4Qcss5B!JF4#tFP#pX(u?}+u_}Z+>|KxZw23m4VESyua8L6 z2B^X^HsT(&(|op94#gNCCqAYd=sK%%VibCu-*CxMh6K6?bzy8#g3{mb_f=rAo2)jPbGCO4faY)VV}Ux5Zyy@0-2)RH_eOJdV1qr zeBt)`au~-n^xt|gUdPuxXbs`3|8k|ueFnn+3)zaJnozbxuzRDwni>b&w-&|B4Z(c0 z>0!5{PlJOZIi{aqnp#ug2N2`{mJSVh7EPg;v6Y!Sm|VBYmYv~SpI*v4CZOK1ag=)> zcp`SeRHO9sCMIj_1AwsCSR{Tl^F|s#n=OBG-6Qs$$%n{}F*9IndSGTuuE}}SW#&fw z^V``9-YPU}paox~w{wZNi-NuKIKj@0pl~D-x!{1T9J#ZR*ax+5g7K|9SW)6=lR@@Z zRit*+Uf~0aDi_Gk`3v&ujCbP@~W`?((HczC33K*074yG}aiK7iwg8_A4H(Ki;y8H;MvI zjqq2GTC#V`3Hd#f)f{X&G1Ta|WJPfldg2S;=fm$-oro5+v75BBr_B1J_Zz5cy;ITQ z@|Xho*A0tmZ<62gpJ=#V$c^-E4dx9xls{_2*iE+7h^*HM=j|&hcw;H;e9_4anDxAI zEyEJZFT;eFRw1hRo{XiBcR%9Z@4<^1CI949{m0GjFE_vyJTalABDK+i9NacTDe^8& zW|t%$G+HW+hW#FAs*KnXfkX5=SfW+$Vg5g1>OIcm+bSPopBsxu@>OObqg2_kJzy*1 zmvo}owhZk8yb$xD#8$5n}-F6 z5f6YuL1G&at=@PfTM)(!JNwmF3((#SU*g2(*jQUuh~T{zh3^Buu97E91L?8eN-b_; zd*}C|;_5(7*LZ!js||oxa&N=>t7+CVUM20qxETg>?`a+R4+)Zj$W@q9mwoGR{eitL z7kbOx=C!pF39FW}iD^h#+d%?V>aRlGk7QGLy}A3wY6VQ*s^REr#&{kt4EAqei;%$C z1Z-_Bj4NVmE%=#Z7mrnK@&jw*vlOnn8BX1kk;|Ro@zGZsSgUFUCpPbLi}qwDbj(=j zO}ckK?bmyX*P(=+xf#eQ%(K@s&Q-F{Sda23U~2^By?^06`Q7fQ0t?I^=2+K>=2a^* z*CuYBA(oU{n=Q5XsjW5<&rF7*hU1dpYu7d-3CW%F`1j2tlQJP*f{Z7PiMCAc{J7ph zJSnZ1Dl0qtW#7B}%^a9!2S0G@)_`@%@1t{3iq~&_oFG|gzou30PDkM@frGm9U~xis zvjCp+*W=;vG>+TO**?W1G-9(tEYd^FAlU&QRUke*T*VPebH6r+Ny$ zbp=8`5ZQNz6@2-9R~)*mm%~U*mC0R!IBxJ$iL9ql>JNZ)sP;75nNVST(9Uo%EI&nPY(d&O`BY(RI54d?;XIBjc_1&H!rTHF zB&z_hirJ4k@#B8rhYNJ+j&J?|m@1J~035um+}Bw4IB9#r6!dFq68gUE_^sK^iReJ_ zlv`j*+i#}pTdmzyBMp+E?but8^y*u=T&W6toi_5%5yE8WW*vSqPyz*5W zC}|*lnyI((ZXt`OG6twNpHeXR*kgYpm28^epu9QTl3bGp(-z zFj$x&EDm_@+>x5+Pxh)C?Fs_xdI@?6FNmDiTR)i>NxaDZ?UKJ|AicTYcM9wkhkUoCq-u&# zxL8ns%cNswZ<4{WZW`y4LVW&F_mK#e@4J<_jc$ZB`6g*9Gwd6WZw5AZyXo25}pLMnk!l(rn0nq-}M$L>oX@vj>Qf%<$jKQ zw2m_CDB@6ch=my*!wMI8Iu%sUZ!+gQ)asS4?#Ib`R8xJ9D@%`H77JVuw}eNZ=h>gjL7;|_uKmAkAa!Cd=kWdfaa5A^YR5~o z?F6X{LGn)^au_32uD?a4sCjD*S!on~H!&~WB243p@`vZk5L9Cc6r}B##l?)*?VD?Q zUH1-yvGq?hTfe5m>U3jcuTq8DUVd_DuyYsAf(}hA(4FBd3imAA9R(r=5HovKuJrUw zzHe^zXvtbBnC^9d`?;da>b(no0xIK78u#l@ex2@+$p`MO)Kc)cF4>BKmC}NEEkjr; z=L-bVTIAu4PS!aLGYX%0>Z)cjZrDRN=q=29+hVF-SjVCT%7cZW#2i@^M+UUkHIK<2 z;5z8yAXDYefH{_OD*Si(1uwGy422b7ft>nlZfS5-!6D^@X?P|dX(A%ZVaoD+hVF(evGWIx_5Ntc>;=)N z{X&^JOMB3wG8=m>Hk*a81AUlm6Fc*X*KNiMX|r%yRB+1O7+GL#*kmX#H!()&`)>4Y zGc+`6U--3U)W*ys5FG3HvSr%McNC^8jq2;`&7jU?(2pQP=dtd*NWQ2B&xgNsV#954 zmG_#FpU3m^d1vHf$)iSEV{Bs(Y&a*n&|L&}J~;or%mDtZhp@+ zrT2QXHYl5T|6b+K+uWQdo5X-6tIg&wuwP}i+P%uO)mK7_MCTU))`T&JcJZJMP!Kfp zhl?mV6uxP9E?Da?QV3~OLyAUpJd8v;6&xDmfju&U)7_G*`tuDtQgf?Y2``8NGJ?6C_q$6hxAh<9h<$rYQ(L>hc~R+M<(HJJAFo zbiYw){VY~LtpKSVO!VTzqRghXoG$h+qb;>W4_P;(zWZYSnmo2$aOXYuWJ$%77W_|4 zRe)O;uI$#tUQPVoX=+TOcai;EeuN-!RhcXYQ-Lz-&(qG>KMDqt&kWJ6@wO!l#Atnf zeU_&l7huZNq&p?UtjKxd5Ld29av&Rc_Z#%=8|dZF!DWY$Pt&!6TCK*Kx9_BQeQ_(lP*BJKcOc-S)joh>Y!GQdt25f3=@juWutAMHHSwbDt6kSP6I6cg;dO;KedJhvus}5lapWF)!scpmB8_jELA6y zu+%`SKr8Q!itws?6jBQZwM>} zra&LVALHZ-PE;gw@Z?I`z{qK;Q-M+HZv9sK@eL zau=rE*REGuJ(Di9YL2D>U{0f5(!Aka$sp}+Tj-pkxpQLxC zm)tf*eB{3GIJe9KOMVF53^Cyz$0()H=gyqD#eD~Smk7c8*6oT~txwg|zk<(`jPfX`i47yAJp}AIoJ@$MlfjnV+qIxR2v?;dyO9ikELc zherUZLt@$q7}U$CD-&>aN}c*HUgEv~R?~H?=hk}4Q@-e6_-O?X0;z3={|?@v z8X7e*X#GPl4dNc9p&T>0TP4<3B-APT8O6(VP-=0f#-HC%{(K)d&(0SKvtEZ@SO~&RlAnA(j;Cqms?*ljfa>^L@ zF!NIpJ>I(nz^{RAVJ+L+O;;U|Ff1DEz<&dpYGc0dOZ7pudk;az=^JO}-#APfli0aO zdjwl~3TH=CA}dTPI*`NfOghYlcZHqE4=yCFcJ&0?4dXfpF*$uCYj#WKDdKHNkG$v! z9omcu?(QyIfY&Yp3Ea__?9ds%Tl7#BZdGa0XMdovVd*5NaFY3a%LU0#Uc>sQA zTHZt*8iINlDz9Lw6Q{6~EXU-U89)Pl(+VC}xz52^A1*N*BGA=Q)eeJabtsCxKcas8 z({}dfduV^g97k|7>?OgZmUQ4B0LrRAW07m+Sd;3%IY|d6k8dk&DaqTR8sA>{i>s#4 zHZhG%;y9;R#ZYu(sqC1ESn*D1H9V4sA4|73!$M2oMLkQHtMV98Iyz0~C2~MtB0nw( ziasQCPoOIo2mA@H0QNKqEL%4`^e|Ja1!;LX^b+CFk^giQ$$HJL*S^0tT8OpX=+%J48E7A(%p0lK=qSpXiY^o5Ov&=rD0Im(s2GAbVlrtT2&eF z?=`_?LUGMygO!0M2g}->-}KPaEVHBtbVd+H8oUWMlu(Zpq|i=pM8-5tNO4jWYZQSV zSMrZf{{+kEt#X4wi;oYhejn?uC23deykVNQ#KYSmuck+czYghGgFH1x)aAT}$@OM< zWV)Ba0@bHe_Uh|r%|Pp?yD@1=c1e6AJHkC0)U8aYoHq3M8 zg+%kVtpQfM?gaNVkeXg;=wP@AuwpfZ(~oUOYOPy_hfJ@kgXNBAP{6Sm8PcyVz8coFSUE|2Ihfa3H+3c*8AUDkw z-Tv+c+T-4@Ghcyr!BRcMY4oj$)3R8+w>%wmM&aVQyS;Xdf81oIJV*hJ>=7ZfO5BlEA)C(hC05v{CJhd5_#9{wOXi>fm zI3CeY4RTiR;9yq>Q`4#q8666vr# zhhijW{jh*cFV_Lm@XRtl3dkpqn~TGBYRHw6_ujiIx^j=!L}|(>)CnQ-ti|EQX9^ge zgF{78Rr!&oA$3F39ImiDt-3ww+X6lJwcX^p5|s^p64AG%ulk^(a=puY$tEU0Z9TYk zmu;ga?O48Qy!kW${&I~-CvaVE`-Hz(AM4t`jsp`%`T9xU%hLcvQ!Y=fM&F`Dln}S& zOzbg|h0_XZs&I-4Rht)~0YA}c%$z%>DK~xjEt27;>9Jr>k5-D=Wx+)sbjmt>*J=)M z#rpO*Uy*IsySyL1AHHZ^(XJh(_ zS~+<>CH_2=r>*DP9@DIAjz={DcCAqlA)>t^0E^dyYQe4NFqzzz*i5M$)rJuIrG_U(XEqz_S_+DYJvJe%?J|wb;Z0uQk*+da8#M3mrsjKzL$d62!|c>nkid4uhr;h> zKTYiv+l?Zhqk4xgG6cwZJ~su1^VeUmHQv>lsXa5>SFyLcZ zy;#36%P_6?p;^dn*8hi+8TTDKC%`+F(j%>lij(BFf)_d&tFHOHu7+)ksD9BWZcFs> z?)m)z$ntc>zcy;HZiZL*Sa?lpVrj2oHiZ%_L!zB3i&_|OtQ{nJ$ZNp(aq9Y2U4`k= z$vNu~t_=0^kvs;MXg6|jh6-HpP1sb2Rk6W;<2UZ?keRNQ$X)G`B%ZnBVX9rTndF)^ z=K?G4yOb``8!v;G)5^h0eRh#(t1VgK-wFG^P)gcoEnwoK9$7#KWA>*}!uJ-P3TD2& zB&=|yiLl;e>-oLR*){M|h@H}FnF1f?ND=VF^P^R4q>&5NgA(mzEw;Gj>ZbBz6yQLs ze;ue4D+c8)o6k{*AFfT%AE8|4;5!LxwB#Km*2i$xcAXTRyD%`2pH<-|@^sNj|Gra; zH%xz_P*ReM*$GoPlzJ< zs{xgGlQO$Z?4hrNZtiNuf$SrEe!$fOi#OrL#Dz2=@^m$EivDW>3Lx%(pWjsacfjDr z|92qqX=s7$-zoQQsY1-Ol%8b4hdh`w6+!>zh+=PdH4PS$c;KV-ZbEbKNN9S{z5BCzuIG2Pd}K&Dsxt&)SJiAGx6J?{46Fw!U(5df7yTV_u%y7bU&3 zli)2dDfzWpORgq59(i~ZE2@DKZp}V$C}PNN6>Lro6nuHl#1-jUBcU$NhC*)^v#0|W zNAfb#h2Hi#5qagDq`fLoDVD@+KV41r=sz>jZ>y2Q$a>j>5Tqa|qBWs=KH&6qoqooN zM^x{V4of}32e(KEC!DPO^56pVSnZBoDn8d00Y;t*xL@I7(rC_4iL!w5R_l{eH$9x$ zj!ujq-7{n=05Cos+NV$S4nX6*&`W^k%a06UaBy45e^DOz zk7DhAjIaNq$0d)Xja)WRu?u^e?XE479A?C|cW#c|hgW%fZu&m(&iSzh6#2hX1oPBr z4~YFv0?~R62E>Vd#3BYj(SYi@4YT&wzN0FXCmY(mz;e8AoatsLXe(-TbXl4P0H30C zh|?fxM~YLqwj@S^fo#nH#z?tFxEuwT=}5MXU(jwxsm*vGRrIqCyk{lAc96)RO=ItZh;mc5Glj(iFRmGy?%?K9SW z1=!?A^~PU3KXc@`7nlWk4*)@m6NAbW^OITDRYm2`xUd%&x4njxdT^(jn&u4aby@Rd zl0(I545r9XWSwv`nv`qU(8QVD(4&?UWT|e&?i|U$mUH#4g8U{UYT)!2M)@!cb7_bd zldR;K954Ud{NTTM691q0xqsZs{=&#@G(g3gt+tM<&~;b8yR951d2h>TaMfTic_O+p z9pPs;@00BP`nMtymh;rJuvfI?^vRW-zu1C&`48=soB%MkF-y^;@hApmhp<6F{I-FfBxJ8}>y(DJL8unN*)2+Dvo`?P%}QFHn&MY4lAqy+^fPVC zy!>ylTcOAFo)NiQJHG<-hwhbjSNbUAoxuzg<>8GJM7&IoCJpGa`}M&h~dNsZjY*{+4jSe}9+7H36l< zv&CsZlwAgrvL2Ryz^!MnaX48F=YHb7qEd6>^k^Uay(W(f>I?*2pxEwaE=t#A%&M0y z1mMD`w4IO+)`=HL7c~~$QnL}4`9_=5CM-P6I4_oEC5hj^77*DdNP~oSY2GaFfAN#9 zR3I-lNYjV|zA{R45|B0*En6jfl(ueT;b}z zyL^Y;IuW8h;#a^)#o_2<1`?_w^)sHOfz84FOxHixty{=lb(SRJOyv~J63r78G`nmp zJgL~}5aXt7@(42@gpQWOlTB3~r;mM7B5uAA{^;>&xELY9!B9KtAo&W2zlO;C#}fEPL#!Lb(sxUfewEdz&HAT)Gz%X z%HMzV`j5HF-~F6_^RQ8oI|0i-y!CW>Q9?ow3c}Jo3^lVtu+(v z#YS;-8+%#MK`L{$@ICkzZ-rJDmkkQ%i{>L96s4M2v+B%Tc4Ev#wnemN0|=l*VLYOQ zqVPB`L%%lCk-;PGh$9_jl?EW3Kz?-9?aA={K3B3Z*cBH`R_DkuB2TxfZaaPqTvr{D zM3$wF`(oMX2i%H|CphBLmiD(Prsrt8ST$1F=}9a?81AOu2_leWoUJgocjeKl$_ZJ2 zLpn7}_@L&?UCdYUR8Y%V{easrkQ^}nC_bWPd2%zz`}>g>rjZX1IhU z!}Uz5!lk6^JE*I9tfVNPV$--n2!aMjT%)uo;xi8_FO?zL=do=TCDyulOLCu2J$X3$ zW?1V&uxGqI`a?Nie!4S($PcL68qjTZG?pD^h?O_;d3?4@Q(s>3|LMdSZH~nRa4weg}ZjN{3bL1NY}9x>#NvYLu2Y}tDbkNb2>xpQog1pn!w2B zekGU8K>*@0UblrmQDhySS;;7T`bkcMcnZUU^A6UUQCZa0Pviui4WQTV!x10JrKf)#!6>D2M zlGs@e7ahoCh%etbThA0EIbYO}Ga;6}@C`QN)q}P6r+${Rv#Bub#gJg8)gSr^&x;X_ z5%g~EtNMkTzT7lsj7?Wlb1ArtC}vSg4k8&pEaLU>O?DRd&(Wa{rs^^*PMtsV+xp0s zT5T=WmEszN?3y#R2G~n|_9D>Qx{V1SgYiA;e|1L?-Amh1@|x1qSFi}sCxq{xG^AYl zBxY^q;I=^Zh5B~=AIJQ+L`9h0FZ-(AjQ?JX{ZpoUuc_fSQ#?wkzRMlcZR*TbnNsD{ z&2UepZQDuK_Vy<%Q)V9b!Kl`OUC|e-v5$3OXUu|)VheLu0grt>CZ&6TJ4j8u(bqcqOq9E(_e$-t@R4b(u=Mg1}HF%*7 z=(?$W&It7*isz{wnPwWF@fHjfPd|mn}m`Fj-akEb66p8bGwcR`xUYg@H<8Q&Y$?V~Uf5 zF7ad2b^>#l$F3UWGD&rMn@~)6hSiU3EB^H`#@@cnLgAdHaanbqGd5a;htgw22;gws zTLxd-Z?Mkm9LyI9jz!m-Wub10Gf?Wg^_jEOkod6-7LBT?{~d{N1OPryR}sMtx%O~Z zE)x8t2K-O1Pw?a^Uh7f(d!)+W%?(XZP!GsQ$n0ZK7$Lx8dgV?r*QF z3)#wBY*ea6j?rF`(R073XeHiso7BBO?{0B_PRo_ED;@Z?TDSPI2Iz#ozpB7Q7S0{i z(rXwF- z!6PN(ZnM++yCqo#(-$)E&>>Jia8d5rvL22&b_g6G{c*5PR%^FX6HBAPoc94)XKgi1 zgJKjL=m~z4R$$Ze=yKq30XKc}O&K!ORkWY>9k-g84R7y}>?3IvKQh-$r*5l5&eANUA5qs{dyQQVrXN=w%!n}G*PS9Z5{g*Y*ia++mWTs&y8qV(EuH*| zmIz2M$ZkZ00la}y_2m(Vu=Hm%`Z+3`AYq@160UVwQ4B)1!P(Y)Z-&}ey0aamijbLR z79S1Ao5WqOL?2IL9(;^OJ!fOx(kTwMunZgdKI>YQH9Q?-922VHwhI}K^m#JJ|QJuPauvsRd<1ZSq*-h`F z+j)2Si76Q+4&ld%rd;wt=c@o3SonnA6P3K(SK3s0Y42+GOnPyD6O7Fft2@CE|YFa3?_eVCDzvOawNBz6x4cif@-Nt#|#Q6NnSNR_$qW{_N{xL!Q ztG4>DgNBF2-Cs-jb&CTWQ=?3XBR_pMsC-Jt+gb=a?)MRzNc9Qj(r-$AmyNrt9AvD} zVDXuX+!0aidfJZz(q7d3Q}nsa#JyxmJB1aq&g8b$yu?N4bDJB5kJfKWeNyA*25;U7`AXQI#89RYs;K=F^`?jUqXmLanmHcum_(^GVz z{*h9>w0A5bHL5o;N}!SL&^-CTe>N~f%eBOd>&W+sTLqF`l>c+p@DO6qCOaaNAQH;H z`{RrqTVREHt5!SpnGhef6^{N9-XY<=#%kk2FV{)|E>Cq#*k~?US}J1V zT)b}0GZxg|r^OoAI&Ji)JU?q*!#CB@Bf6EQI3aLw)-FLKV|be!VWo&TTZ%_UJ~_H< zh~#Hg?s%ryC&9y+aw-&9y}KZf`VS*aDf~;ym6MsfkuPBwW-Cn?30$&V61j4MGHwg& z=+M}+Jokd71Fg#qZFm~ihqF9#(F+2ZzFh76N|5MKXq(m&bx7#ps^&LgMhG1k=X5{H z1F$BBCdN#?L}Pvw(kyj$a`tqx;`yb^ML)x0Ka3}@nCpU!UyZ~hs-`&K32}c+l|c_liMxV4xkvVbb~qnAkj6olE{%4?se;i+(gBhXjT$tX0rt4$p6y zSUHOf7$}}lr!tYc$xo(VBEKgFbw~JIv8Hdan#oHSzpi>j&w0h$Ds%+4x$1)$VgH0lA<*_xME@t&yN+ zYYCVBXj(83hpb^KX7)VOR)UrGfWJ z9uNiOoXvCMr?OrUz7B6T-?6$syLNS3gnGN3w|R(C$+AGeOhwBpRFarxKnbSUclXh7mjXF?j|Hcb0ObBd321H;^fy$okR`7XUnUhSEdzA1i~0g(TotNsA`P5^xgmu7HY#eb;>Zs0z0 z!DVED(ooTku&VBI(bH{1BmdaSJZlZJ1m!-*i>L;5`uO&kMXJj2pEm1^t*~SVa3+QA zgVMZ-MvC4@Q%fo@)_ws1qc>&QNSyP2FoT=3)kp>3;HaM-~yp9huM?X$8^O;Bq*86LXxYvAIiKHaMBcVA9z z-Lj()SFLZG=eY*46CP^iKQkIFyO6mjoHcuTtPrmxtvCm{{2rKh zTfe#S&)e~{LVHmHFa`j+001Cq+FSj>InvHPrMor)=xaD+x_)iF6LGnu^=T*dVUJ}h zXo(Y&4!qcQs<(c82W1N`S@Kiz_19e3b5Nzi_#UMiw}>l^cK5)cdJ&rqo0wfBxNyfm zVKaso_mcj*aq2&<=f4WB|60#NIp|*eePUeAsVfo_S+2cew69FGqy(P zB3fI`bS;ZlhlR7~+&_81dm5GARV{9Xu<%(w zK?6}5C&X-Y)#oKHRAox7Z+_=>srz56YejrKE4{@Al?gL)K=TJ6Qdj_d@=NZak72d- zZN{=eYyah0-2-zg*eR_%>PzM7!{Ss6W9WICh6AKl6XH;{d=*Rdsa2^|nu%~aR(TBM zp(<-i9dmskNGJLUEqeP80Du5sx!?aJTHI3F>{r~YYY~%CRA^K5uAB1AdP~zWXsBCK z;B?#wDi-Rz<%DwtB5g^u%v5a7m|x?r&mdxwpA?{X&bZ&$x`4sQsdu4lMaeEptFb@Q zBcaM)cFbP-mv_=25ijLz9Zmozt~;>?#CUC=xuC2ifv@zu8kF>w(i3tmv$M+bgriPL z?AbUIvu2yh{WfI$hrx}|t4=JjH{!8Q<>&Sz%MI|X)afnZ-o1DRJAJCW;wgYn@HD~B z@m33J*bXi3Z(*%0W#L<=sJF2%FF!rqX|YVCeeLirm+9LXLxG_2E5s^vI3pdy z$=8H3bcUZfR8dFzDZR-fr@6J{E|#Ttfl>9nCZdY<_pH*c@hX~qKWk#LZC8b+Y)^W7 za_V+Fy-Hx(5BR!Tt{2ob)Qys;Q69L(@etUPzdgTFNg^V16Wfi{2|eKR?iSdnb#$HJ z1X(xTsZC}8&z-`MnF1(bl8=mLfq{5R&QG-zi8UAIY2&->&r|3~7u9t;fUKu8JFqzJ zQ^Yf_sMZ4)CuYv_{so<;)+?g#9!wS^ztDpdyN})$Dao?JN3g+X=e$SwZ(c8z{>li; z^8QW~i)4TO{@c{|uZ713$Dtfiy77zKrj0jfXy^Dk#%i5~2@WCtqG0B2Yt`P1wECPT zaQTU|@4Fyb1l0HfPTw^`_A(GrFRLh=zY=oK^0Fz)kWps;flTL0+1sdYr0B)HMHJ+o z{X$e~Z|RJpxK)yNVv|e8ncg};J-#JBaj`<*cq!|M>CDep_4Sq_GL2rzBpf5jJ7-5Q zH_eC^*X55;3E%nyUy0FrH7cuENlb&lBbIORtLWcH>Zqc|kGr!;ZtqU=BBzQR2>+~s zf1ZS7bGyiSgRsR{XC%Z8x>(QmD;l2eKM>=46?hI zZB}lBg^^KL_Oc8(NUyHg$0U==XMLF@uqHET7V`S2{AVBX5nKjYp0kB;vs=}PP=X4v$B_@Jr<3bPj6OV zrsV5Lg_t8m%Bf!@FH*SbuMGx?m|6Wi@?ls{VIf);tz9>ec{68yx(%qyN+33lahUM6 zOc2frqNnJ(T;$>A=Cl-KOj7%3x4E1roZCn(KryK*g0?>Z?1Zl|6}) zzMIDA>(%l^lPSs)O2;P>!w3Bw!(FmgDIfH10|=on<0DQ)G~CS(3yz%VHE+rBJ-;R# z!<&zio55M+R>&2_AtG9Ag$0M$HZ|kEl@e)uALJsu^O8Ll5fr-a^aKY0^qcSLQhu}} z_KTy7_-~FP&HKp}h}LIzrtQ-wj+1uY8vL+AT^|oM420JUHTT2YgnxR~8q`AeqDe>n z1D^F2$FKyR1;~{W94-g;QQ?qm&)Vh7 z>7=k5aLXa~;#=>goo98lYS!~IQ{NfrVT|Ha8Bz|)Iy*zLt+v_@L|@S_6G^+!En87E z!Io+*sE?$RG8$(jk@!u#-|lZf>cytLR@MFoApP!)4kttKf3^0OQEm3^wl4*WJH@?N zDOTJ96ewPxxJz*eS}b^R_X5RRv=n!DFU380a3^ShKu?~1*4gJB&w9tR_S$=Y$bb>P z0Qa5b`p;|5-&9Lg9$ih?EUDb%+3I>evB~CGoO|LF_n@@?`UTd9QgOwj)(&n+d@97) zWOVcTEZniOivRofB^m4aU(={<@1quOz56@O?ple5id^yt;lLtL--Fl#FI)ZtYfjg= zw$WV{Ph9!>sz{}?40It5z-Px95#h1CDhPHsi(i8%B&N)6Dj+??1AE696m^ropC354 z!6(Xx?ai%1Ll8k%V#DLsHpd-{eRlRhS`*uKQvNvo%)P;j;!LMx(zxQZc|l2AZG!WD zHZ^`Ow_(5@FPx)x?kWLU20aRrJP*Dcpk%aDSH1orfkGp=lH>dW7FZ+Wl?@a zX_7DCFL>{(yu%#UuTiUH+wa#p?RIgK;eL z5`&-JxekxH=-qJnk8T>~NJ7*GwtoJC_CWH`>~$yK3!66CMNbb9Ka4zMFq~5W z8E?0ps_D zD*_WLv9)dCo^|&YP>!#(Y|tJ>APdGi{S4MM9A}-8SUKkYt=6k3_)_qorlLTOo~6pjFp! z)M}=1dtb{9o3QavS~mqCvw=HO;NZ0UtYo&*vSnuKWn*gbr*nByB@PEwbzN3l4WtvL zp5MNeVVgh7pYUOo*QeM1%mvXaCd;da@6v>Hk^N+--`^#+K0>5Rskb3NV3faM^4(7& z^B(JX2*>=r;Q^g>f_DA-c1-#g(u-}B@b?7x=pp0Jy>%l2&RcY(@;DMK^JfAICx4LU ziu%u9mJafw5b?#Al>XGsWr4EfH!1(HZ}MXIdr5|@zwP?wvpF{()9J;~>bU1kiCN3_ zcnf;inMV$=*A--{R9BzZ`w1wm;HYgAe+7>DJT-cogyKTz(tKi!-nWc8iI^GC`_|~r zi;C8gyh;q#6@NZ{X&4=2;$%v}uKYprWSlW5p&>#7Wq`6qW^Pt4Yqj!qbS+WJ0J6p! zS928|wuhQ_mTp}Kc??9o5&+JHcF+S+wMYSM_E$y7 zQz~#~LJlKDRKO`1K2*pZFkPOSGYCHKCvJbcQ3?nh*{k?Oi!@Vl6(@1Jr!`aVA;qC? z^mGHQ0Aob5a-cdU2ENy=vRx-I=o>jdjVsWTsdtKhKC2g4LF-J>v|a7E*1k}p%=vTL zi7J=b;SwiHDSyUoOYCc?5K(W*OF=7&8{vu^1m%)#!Ew87ir;rk8Y2Lnc5|T=lW@k% zh61hNCIcz!p8tlb{Jb+g7-OW5i?Ogn-J=f#=~f}eA^GTbW{V9PzTVcb@rD_rRdm{h4s%=JlFYBMK(t#zI1SV9|u0T_pfgf zQdQ8)m-EJ?d8>_@IXMg9L)1v$)9`6eI~`YXVd#C&#AHkz5q_rrnJM}0hVIn8brS@} z9~j-qpV`0P$<1EEG>XcN+Bj*>0bv;b4u|veKzX zRr}@>&w#@cjBF4bSMCK4jA)=LvkTu&V^_?f_9=UvC_mk~KE>gF7D-284jqpnk}8J* zHQQ?E9cK~*QHcheK?N&F$-W!sB9dZ$bWowl86{{(zI_w$d3w0h+8Pfah`}Rw1uf3& zqXOzitOtK3Dc~m^G6(|E-wf#k$0i%TQPg&1;iMwLA?Y^#l1jgELw_X@&F^otVg$yf z_WGGePWof zEX<}0l1UORILL#aQJ^>Y0}%~GzIrgRjfC(g)+I;u$oOK1v#)_eIdCE2u&;Cxb*+tf69h1L;-FTjLy1^KEUF$P6GOt`&Sy?H}dGo zZ&515qK7h<4@6Vf^1Od88v+_V(Rf^6#}>+&>xd6ELNG}3Oto!Whx+=@*9T~nWzQd! zH-8jO&94nAM`@1r*S_**TA4-~yG+GR>fP)3)X4>N+>ANVRO>u{DlLI&jA~6gcrXA< zSAI)(EPSP5aHWntECwVmO1^<(#}7r>8F1BA8T-e`R5#B$@#JxVSu>zV4bKGFL04oXJ|tUh ztKP0Jr+UHCd&MFmt1xXy9wiL%Vc$2p{z7_!=8I3Lj%a92SC$8*3j+UzIdR4UVO^^cIMm^Jb!&Xa33 zJo`!e#;EBDjSCqblqYZ1XYS(h@+Ry(62!KhlyjBs%3hExwAqS!N7CIVVI7+g_sJ0g zqgJnnuj`x69jkka4^ z%U|U&wkEZNgl}){Bkrke0c`Bm*Y{iRXFmdgWr~z+=C0xLGT|Cx1ai1qRkAR|@qt-) zknV;Q@y*njiO0XKl>bM^&eZ=*VE@NfJ4?Ci{KdVGV1r+mEotJrsGYHkc3UZ3a47F7 zcyWZ{15jzGKY%iL^JSiX$T5&Jd%K(rP2;9|h<560H&U$)Alo$_I8@JxtqEl*!AmNX zwrR=Ucs7Z5JX9OM+fnishV5Hv7oz^O7>{g~`!ZM2$t)GZDi)!UabR-7ar#qH4qMIi zQbM&0>*MzJj)uWqg}C@kF02BQgWcb?6u8(VY5O3Qx9b=Gb-m7cU3=WcN5>}oi5qm= zo2g~Ml@ds$zsx7zYGf$ocH?D{JeiRcO?^*olWpA&0kvlohGyS6=t?vT|0w%C2-z_y zD-$R?xureTPM1NK7WPr8uj;&SyYmvGVTa(y)|(Od@Jrq=N^_2{9wqv(2Jr33o0)tt$&-O#UaK#(@ zC%7YZ7+kSyIQ;84?~U+?tcVegM=7cp%O_M;JsflY3@8V7dPI1--lLjkBqEd47gd}_EUBiARG(0 zJ3|>3h2HB{3HAxxCpfffUy;UI!UN1*mr15p{8XVk^IgTDVn=h$GCKn1iiTl~S`MC5 zaJe+%zkujiS}_zh2ZrQ-ZrNXBgH>!fliP*J&Q(GZ)Yh0moQ|6f@rv@$VFuYX-^)aN zdhO9b1Z!m{lv%b!pXFrxA_A9VB(#ZDqLfVv4U(4d5galNz z6tB$L!;Pq~;xX^U4!53wDbI>&AS_w{yFTUi?F7`i<~o7E{vK9CS^or@akQvyy3_VF z>{onu_jX)}R#ov7NEvP3vIvtO>SQ|yq;qZ_)x6OCt^>g|j|Z`6i#*#()mIgxw)7Mz z@PcX%rFSydK^^YDKTXdh?rteF7hR$>-ds_LnVLr!yj7b5j<>8 z5aMi>X|NBbE!hSSFe+n!K4yGNy0Z=|l(QZ60?St!Tt*{)G20``#_v`3O_^&I+FBV7qrcU~nIwo%j7vUt*bm%r8BfTtPaWZ1{g zSLcjJ=R+#k((aa(u9MZ8U6Z?J4EM05qlW4`JocrZ}qnMBJEveqJ`q^vJb+K`V?i z5}#8Fuq%T_4CGQH0K$k?Aw+vzPgqI(upDQV``5I$8hRVAV)Gd;YH(ywCb}kkOa}rf z>^>doCasj;-k*#Wt=wt^&tR7CUKX$oe-LufDy0G;VJ*)+@vNC1fO9HSH&ljtnt73`?veB0V9|y{9J`H3c>9K*EHI~eHg0elw zKDEjZEFXze_Q@{P({T4k6|xy3s=17Jf)s}nK8ZTV(nc;GW|^G_qf-o` zK!x#^@Y&&X>Q*yaM?rr8S?dhIbxV?9?cl@=ptfCf0cJgp&p7lKfS`7sg$KN>z7n~M~7j!XZA_iYl!4~A>0tklC;Ts?&o=v zPCB@DKgW{6WCw}(u(bD*x3yA;{$r}J$f58zV)-Vx;(5Kv8yIlIYtpgrsFf?Huut?= zpR!{qcRC84aou-Q7@feuy7}bQI;9`u`AyhPLuK^+PK-)Wluct0sht}2?uVhSA5~xY z5eRRd^V2$uih~+(`3>cJ15tLl6Dldp-%g`l8QRr7K}ZA=Ci!(&bTJZ4lPu>eH|GW` zlHp99X)EKkLYd&t{hh8vY|b~HClW1%lcx#VZS!#TpP=-f8=u>F_X!e1nok2^4awE+ zA6ZZuk3a!NL=w=WndYlJs?jP$^E=+{P-|mGISeE|R-A#`mFnsW{K4PSs4-dm@zv)u zyw#fdPSXz9mlR{Wbg)gByD{yaa(nnSQ(d^bjV<0g=T5KRmK%w0K;$of8p0J9=+g&e zHyWFppb=l4Ik)9`I1(6Ats>EJs)&iJlU;MA5Hd%H%_cX6AKUzuDHX6WaR~U)9|h5~ zJHOFL<;FE!TMA5Q=IEj1P;kxj?4ELv)@ zWH72yt?k;guGVH$?=JQN8;`awA%E2cfIA!0g zGJAoK%1jFNtA!mXV~phQb}CN2#nnSymqk74nHAL@LISId zgpVdAg?_})+y-+2Y@G&$-^oJ}U&3*5;)vZfBrdYurd#tqKhd_#=4s72aCi2&IqnQn z%TSj%1V#0@cwX(%01vRqX4(mh5zSdm#H(YjoP7-OV8z1JZOq)Fml&pNN z)9|3C*G_bsq?lFQ_f$w=duZv<4@g1f!~iWxJ3=E^^@M1FJSHR*Qf1JksBj`sap=D4 z{x(nY|R-o09UK? zT_JUFb@!igz)Y?qc)bKDzav^QJb*5uSClpKI`2hA*g~X%dqaa@0zKN=M)w%g&qb{b zLC=&$wn1Rw+|x4?&bYAKFb}o{fsWbD>EYJuEHzq(yCo)T=`~BEPkpE^t_Ca9LlgYE zGgBbOo>sEHKS+^#@(cM;+}#}9LL#R+TT0C5$MzMrFFRh&Y@|iFvPN7*`I%Bv-bNjBo;j3*$9`GwijPp& zq4yFkX^W})B=SBdBT2~JuX34}z+1LU*gV0b@j9V>1*O*E?JMz5O*JR`iawU-tzNyC zpBC!g=5a%44J|LF#MCEsJO-?DoG#bLFV|($GCbybh+`|v z6&uUxUmK%MnwS_ighCCy?OzNtv^gj1#0skY;(a#gV%z+wZcmi`&>7i}{+wEZ^V~5Z z8ZLFSoJX(>UdQEe*alA-4H_|}l>dAYlKWN!MVJD)#STs^ShZ#=d+P1ghVi9Q@RRU*t_yYq+lCfGB^x@v^aYZh(CH*^FC+SEJ*L{LGkb;C z4o#Vv<93*o$pbLS#qr+Ax=IpBP}!{^%PJtc!p?{`Pj(txtJ4PG(@S>5_==7YGExPh zCk^6~Ve^gr_>an=|0;I$Pf?@)Wd2HlVM2^i-qy`)8q6}(+7*U=OYL>Sq7$Ad1}+BR z^;x-Ytz#cmi>Yv)%r|7bk3HP)1ieRA3aZC3bFT{CugZgDCwr$6Zd&Lj-?lZJW?NAS zcoeNZnF^e_?7FHwGw>);RH;wy%&t)4ZJj!@ZN6F$NLUTv2plfin7DQBx)MGD!%?>A z&I#+D(~Q7BrXVAOeOW?SOGC1t&KzT%guYk60d zc`bXcANtLcCe+5nos|XpI$w*Mj6z}?(R+xbiW$2?SaLt^N?f&Eo~jIDolptx*@`#? zIEm`|mIX{^mG{%qOmjI_>S{O6g$P z4LD^O+zKH118k`L+TTcD9E&3*Ca_Q-p4G zqbb3Pjwe;QYY1L&7ApC+ZSuNd6wloB0>GRfF-V8i2lNk(4Lok9k%64lwF zJh|osgSia?Bjv{;ky;Juh-5LTdUiqlU&dd2DDUp0IV7xorv0>AnxF}x?WT4mKb1Fu z>{L9&ip{DV3l@T&aalb8vy*E9$0rV%V#8%akc@(;!Q#w<%a%o?1|POxq`oqn z$nc%&ifhW2mikAd+b%-5`DDY_472m?V`nZ%Yg_cKR!eJ>RopFN5$=Fd?wiw(hxP{7 z8g>Nf$cuurqKDM`^X`uS66^8JTE6W9Eiy>Gcs>Pa{%r0#!diL_wMSeo#0TX6DEs<9 zdFB1xrX|q=(=CwkI`1@G=6wC@K}(_%oppxXjG}Fg)GY5D7yc-+D#ln!IQK z6Q4;~0o)d5_04Ybz(A#FuA?3Ws|@RjDd&#qmNmtgCwX!^+oY9F1g_W=yfEZ5*HI3{ zQ6HDif2FM+)Qu8kTp4msH$Zte?Wn0eUEUz>^rEG)x~0Xk73lrUDlxwR2`jGKX+<}u zVb4Tho)aJisZi6e#^F5O;mR7MLd7d!>6|{t5mhZHg~>olqBtadiw-6W?_&+(nrEgs z^KJXah@ZV_)~0$7yh!Ug$lT33_LQ>qtFt>#*K0W+&w_Y2IC711ek@cL=HhzqHV%Dn zW~I$m|1n&d?>SOzBB->%Ii&MtO+#bbv;eMHKbxyRZDwGd4Ig>s?nFA(**iKxJaVGW z$JZ-#1nvCvF#qRIS68UnpZA z>EJ}ETiXn*m)Cu+nWizC)B<7@Au$|W$I`ilNJbyG18#iqLcV1=3;K>2h=KfIGv+pU zJ2_1RNaDC$rCWhvo)YBH+^kK725;_mBZiMR^+D#17pFRIv>MW5g8>en>hX$|#migs z(S9mh6r?Kh%F}Q)=IqIp7KgxoXg6uE-Q3uDubvzYoAyVHz{ z%6Y1oSGovubgsQUmP-9pHqw-W)(?k}wpcBbmxx410`>$%Pe7mZ3rL@nc5`l}u*;EN znn4Whcn`3Fgb&ayJ6NZ=5>umGw+Cn1i;jN*``v`g|}6IH8NECl|@LP*!CKm z0P|?N*_q5ww+SlLr^47tFpK|K$DnpQ+~m@P1$|Hl3eEazTi}2kDw}{hvp$Ym-aR6M1^bC9d~PvH5+y zn%khVkFmP*tx~Z`;=MgYVkr%IpBj%Xf@Z{lhln1}o7j6JQQ=uf%UR3JQwvl+jzUbE zWV2^9E)`>T7_2Q{4h=2+(4;i9!!b5O6m`xJ5x4uTr&h^JfqgJ{k8@NfLv`J3eH>wN z#D@wtZ4eoAn_6n{$g z$NBb3pwuRxE+`O+0j39Ihyb>3Ow%u<(#+8mCLAECy+hIJ#*G!{TZ7;iUc85`@2fd; zehVgV7ZCCvn$RQHFo_0he}3(Ql4)IXemf{CFxUkAX*am0hR4AfK&kKt=@S=LWD9Qi znw}Y#8viM&@GtiHCptgj^r3(kQ0?6el$FDKO+EjNcTv>7Hr}XXj3QV_TI0${?9^~J z*k^zuLio&>TCq3BZ*bt1-FeJ=^0`}vS8+FWql*U15iP2It$ly_(!gB zT)v#5%2zCUG*-Pep^V-^`ZQmH5Bs_Bj&);Az-lFHY9HN^#~c~oZR#7L$<%LtNo8dG z!9+V46Ba%D-wb;H)3o(>Ba%c00-EIOy}3@bYS0lGr#*y}T) zn=7{z${br6v}ED{`f~o3RT$J$NV?3;rmF}DSUkM1{7U1#yi|eumE&bn!D@U4Sk4}i z2xAU-g@A+%>^-3>J_*#@GsW7dv>jTlBccx1Lyzu(yTCK|fB zb;6;iM_+ruZtKxK1@6Z_7qGe>T^&iBs}p-~o4U!84oB5jp^?xb@q<*CMT%c$IT6BJ z^b6Iia4neYW(C)pIMy#OABtP*_O4pI%`atpB0~bpO}iOBcOG3?_fHC5u!RlTh1sGTeyk9se?eP&Igj-0szPK zuPSn!9`#5KHIr|&vL5pi+%T;sQ5%F8gS>&fo`y)7-B{@cyjfnKL+u4sri7W(iB?WzyK;;X9z^?eMX zqlvy{xEZKkXl3x%^Yo3dkDPJ2RW>E|aZFE&ElF>6n9Nd{sC}sEjA$d;&t(*KQGJJ{X zC4{d6GYxZG=IFSG5HYCHvON5OBIC{MtXHd_Ad_+nJ0xJ!sVFtFT4!& z;UZu|imR+ti=C{%=di)kwZqc|>8?y{QupR0PAki>t%oDV=e?cHNSLnoW))uFrv5b@A`$8z3^{P#Ze0LG zp0IjnlSI#95Od`D{x<)$#GzmSacGy{j0B+aU(In$zeDuDM19i>g@zNU1yc2q8|}G$ zTZxISra+43lF;(|UTkMj+yKh-jI$AVoXuFqMXY<{;#EnWv0hZ3^{(SKI68k=xboLn zJ5rr>_vZy(HG;@hn3C%4le`_nscxZz{?EN6u}-YciyJKy5JY*jt^#2YYp1Wv)bFF# zrAlJwrPF}7a)#tvfQ9cSuo&q{fOeJffdu2g1e~F6x%g4m>1h-+&Be)C`J~CF{%eZ^ zQyAcK%9N0D?T>AE{y15)Q2!7oxc$SGR{G@7-KUps3+t38L{qmCRBUN_}3lrF4+8yf*>`-5c1@Az0{f%Y9g zZJ-9{4opS=u~RdVSNw=iCM&;#WoA@p{ax4|3HY`u5TLQL%7Xi_l^$!}`@)2Q74nqE z_MP%eMLH~)sK+D<{@>u`E-RK6#XQNI7GwKWx-(A%M+<7@5(TApt>U>+e)kSdUJGci znA&1n3%eteZpH;uw5%LXRXpZU zi3yt#2WXXK8dT6%&_@~SiK1OI2OkdYbjst&Wgg`{=FoQOb5aOcbLq+t^G3bdRBdeeU^DP zOkuBb20_!$*jSHj{eu*g-x?#Ur|*o#clX2a%P@H#zQIZ0<~KhCIT~>M2kC4k5b9CS zR9^W}SaEGA_Fu-11Jg)496x`^qfn2*P`JBToZK%-T)V0InpdxBWRgV~6EUj3?j#kb z?gX|Y)P~aCf^yZ+iF!_^;4SUxc45}|`9Dbgl6V`{HOab*#kS77yM8tLc`|UsUeP-96QzwE*5_|E ztVr%C;$iG4HsEzCID47VVJE{J1m@6`2AN|LUC;Z3p?8wvzNYs@_Mx3H?yHzLS}U%h z3BpMNjo#ZVi7wWo{PooPAk!Ln_wiG8?}Z@`B8KaQoR?ezfIZkgtEYV*RJ*{&D^Kcfnuu z?Ef&s;v$Lh65($MRqMa?k`}NvSaztKVtAj1j{jP;(tmcL4us_>cX7bn(3ZTX^11bm zVVnMU9Y$d`+;$rgkda$U`L*{{W(U7Zqrv) z9HRGtR2}9avh(}k@VsJs12I{_pSxl@m*%!sF1%XrE6s;#VsvLZh3e*5=Oo_j`w)p8 zRXKil3V(@Ht3QBhzN4<2hQi*CvjeK4uKmka|JmeaNIz5&=;-}ZuQBz-Mu9*t`h)Ud zMM%@=YfKc1Gew@o9S^l&N#ZLrC}4sW?D*B&HCXR+ZSrEz1~L#`4d%EN zr>jvr&42RJFs4I#OaMb2i@?$Hp|;Km9PJ8!XVIl@?3^IEoeD?%)m6s2e~^^7t9<6n zy1HOizC|z(6Jzs0446mzkCrrLrnJC%RP{>&-nQ;>-MXi6IH<+4>W9z3E(v zYa6{2toBc`e08W;$%A7QVv*>(TOB4){9d|9lNShgTuz`eX=7U0yNa6_uh;s)&l;Gl zANujoX^tw+P4b}VYP2{iCL)gQe5_HNlwh{r0;+EZ9R)g%r(^9rbS$S`0{XCyVvsba zX@8SXKv?Cny(ySDt+gdtu!dtMXZuxZvQf$I0Su`}F}j-KXEn0R}k}-e=Ecb*O1{37+_SagNkSF@%mVN~XRDbR}jw(VoB( zruo_NTbe5Bqh=BnZ|noL?%P~!Xlxqx&*HFvdDUPk;f62$A7aDto19j>#@UI@tD*8y z;q`v@a!U3f(bG^ae`h(0=&FHU@9#|dqYf|yIhKl>(^R)w(H1Mjmb#~aF5*ANJ{ z$uW-8>33SEXuPVLsFsdHfVmD$2!4XcD200m5P>iIZX>wPiybpk6m^q0+CuXy`wgn4 z%Ugj7dr|A6LSupW3Oml!Uq}XQTed4`6sToL-qg`kAqdBJYiso2vV42yF65aTwW2el zSoaXJ_J@{NYI4-RlYGe0OLK4$2p#^N=`z@OK5Osv;%Nq@uggiuaShEiDcgOx+#W%? zF<>vWcP^4>ZX9<0IzpH|c4)M}HGWNbqg!&d z5rs0ikJ?wx0ttB}`@(e)F}oH7_g)ONa0a$_6$W@mjk(d<6=B*nHJ@CZ!Dw9}XPEX+ z6uE2-rtFU*g_eXh4vzHM+xTZ#4}5)z6I?Clr@nL)Ms_ocpijNfdOad*-$lo{?IZ## zGwW0-X(;!N=?&)uSYmANE|0NT=y>^d&MliZbuh5k4N~)S9}Ka_ zNnfK2$M&VrCVbChm~3J6{MqM`@eMDO@ZlXD*dU-CsLq6`~AsTC?~g{KA=V*l#@M(xp61-r!wgalm#YBJh8ci(3=^% z^tIQpfjD?XR5(9Z;V6G3uC>3D@n zTHtv*Q?LkDigTs!6D?1yejgP33dxfs31S*4!2$Ki*8?_1an`#v3`etrIA_K!^+r>J z`B9H$ssA8p=1C0I{Pt(LCxbKO5qG{^+1JE4;fC}4uumg49L69ty8DDe9qAhC|Es(N z_6gTT)UyLZM+(P-f}sAt!Ks}wZY3kdE5@V(iSdvy!EXck&-EcaL2-d&dxRv6@`^$gBs8` zg{|Nh=5O@mvGQ%GZ7ASlV$Y|p{~!$nKG%n9K*uw@FbEJuU#mmKKrZ1W>qf{m7kPZh z=%uFX6En(mm3;txEfhV$28v~*SfID+Cz-3a(r_4X|p7EdbD7U1I3BR3=>-W_=4sH`@t&004zm`dmi zVp6xH3jCs>u%u^`UCntGtwYC4w}pt^L65_;ARRITSivfTPh_Fps%r#*85>WLK~cPp zj)WfC7cf`XB0rIt>j`{;$J;o|_F%}q@fcymXsfLSXiKxcntVLK2rq+aA$b*U0)0e` z-gcSysENx)`Y9x-g(AVnuTmAFNzfVOFn0n&W3xAwKzjqCzP6k;zB_q1Vk0F1r+nM| z$=>DOnq6FOm#0?;17cYF_qu(ic%77>!__KIent1M_2MSJiqq^ZR>}8k*ovobC9I`? zjbWghp`dQKuqQ1v(BayzBRO-o3w@_SmV_ysAPn}+$-9D94PV1`(mAMb!zqT4Vr@va z?oIn8DQsiv6ld2K9>qNX`Si{WKWj0C9LELQkXyh~+mv09gy>umXng8pSmkiCnKU0q7O@!@%Bb zrO=r=(|t<*As_Q`_u#|EHC>!AI7YZEm>b>6+%PXImQ*r`E9bZ;ch&aptl$;j~7PLF)>8O?yJ zcF8LVeRfH{>F+uu789z9l}B0ue(I*?FiFkina#?UA27y#K@*~;tO34A)=nMpeDMme zG!M>@aL>Ldt*-|BRW%gwB#fvs6F~;8tDnO^$T~BBu_|b7X~rb5?NMlsCRpxN4++_S zZ?y0Qwi|u_pmKh)-iIH(PFQi(AlPL-iJ%*aXP}>MrHrP%BnhH;e(66MC7S5v3{dTx%dFYNO@U7a&tD=6*2!L34Mz2 z*t{YD+^X+`GmM_SNa}6E7P&{1H~Ivek<6PMQhKcShm^uJZ25uw=P#;00?HoB+?68; zl{OfupCjd^Ce-9_{tEC_H)(=u>Ox6NSu!(kW6A+K48-JPk!JL(buRvsGPl({3*Qgc zft!1wh?hmg+x9uQMrOwsN`6k}>toI3iq0D*8Jt&>b@jktJ`RR zFQR`|)GGuovY{K^3pS>m&}j1r7s0){H5+~ciY5>9)N|9|e;cydd{pa3^t5`FFT+9< zWBR~>(SGtbAPT&l8K=xKS`n=i)76X@;YLV=!{cbS&BuPmvc9(irnvSdL@@7-K>Q;e z$ohxcmU+lmpVUm6LfS*AU%4A0`fIr7&V3IFC{hTl48>R}S?j#&mn1#@*T{D~!%Qq% z&TY-xID0`uEHTkTQCd+rwT#1&zmq=eeOCCOil*iSJZyrIZHyV}F8EPw#>sqmS~)-2 zNo~Je#poP;ZM?#Sqrv}dcBl)`vKrDoLD*9Q7oT~e_4`nFZUjylv%<}3M?_zLvA;6h z?@s1^#U_<1qBGJ%ef`47ujCivZGu<&zu;N_BO2`Q`x@~X#k$F}t98LkgYd}_3unw& zW4Z7TkzVpI(|*Ap{PB22i48=72TArk{7pXYav}~z52oM^uCnLtt|OG62RhA*)TBZ( z?+%NCF;9z?eQm+|mcm+Z^}pUDi3ps{On7S;WqIe(>4tvE&iZ+Mv3%d82@c@32kVqR zCOu|&;j7QKK_~g$16Avv?|gZ_7j=S4U;;89$EI@mxy%%d)JGIN zbKYSojD_;DU*JU%`( zz<9c|v!ZMEI*t`GFqmFsYO4VQ%FXg0tx8=pIJ@DTP!o$fEE6bBhH7u1B#Q&t`vVE9 zu1|3Ngn&hhwwd;8R9kqt$%7RlQ6Ew7k`-J?K(ys+?cO^iLl!eFM_}DSX9Bz8{)ys) z`-H{%40p|&A3M$xzvS15jFXF%{O1_y*hEk-^=VmPS>>RZcWOA`MLk=Cx9JnUDuTqQ zZc1n*I~?PuTJ9_T_`ljA(K^_m#Pt?`p~vN8yxtEq z%z(oHblKv*dD^8)#El}hRCj92fo-h;S8Uja%va1qKQL5dM{$t z<)%tqcbzyR0O2UYkoI5EY|(p_2Xti4k|>C2R3{76Db>5^C=TIhcbe^LVEW3S4>)6bOJH_P_!9~FWO&HP)x=kplx9M& z12zCL{^-X}7g>Jn@dM73BJY%*SYPIn^4!P>8Rq|8*Y24bg^0i?Yn*^ETnImG8iRBk zSEWIl!7wm;s@&JDjaCoq4NvxMrF1k0nYf;xIfBu)5ZNwepEkd+#+nw3@Nm8g(Zv%U4RXTlgF4oMai7NnCqJf3O%|BTpuXT)NC?hg|FE_-fiQn{eyzw}7{zbPh!&S`u; zK4>f%p zQ~=2++m)EYeUgcOv7eu)#(t|+VQ*!)D**(0F;h5A^RBhJcS= zZ|ARkW2BsJzcH8fC2FuZXDcaFCudv9YfiVm$WQvcC3xyeM_g5(6=yx6GR>9sAwjW0 zS-#D!d3+y%Gk4*ur*kSaTTuH6r>&pzqXI&V)r>(-&hGoyN>+LeG=y=Z~3ohd)68ba|Q_Kp8a-C7M}aIx`T(!zyR$Q2D_%vg+0AiE3o&ux`93hs}UaR=;rU& zB$5dlYDn&ennu)z`ecimWtgnbf|;F5_StoY+hr)F%YGDFth{B$Lk^sdQ$?(NwDjZp45H!0e9 zM3K%@iO{`3tr`2F=mm7%77o19W0Kk-WlYwOq{cT8psE>yad$VGFiZ2ODQTk$ z>UlC@d9?&Cu6o3Q1(oAluoxjK&z zjqtUWJJ+=e7<#`6!5lM`B{VJ5De3#6%Tn`ngW+gcwEhXqjwsaKy z#350qR&8l|C!Pm9^2hrzM#7<)vEBCt}+H;Q3tVo!N?aUXG4T#7cs#WEjf@W@vKH(3z-nab;xI z0LB}J{+jC0+i-q%F`bS;m@H=nt?-<>WwWe~hgpmEr%(NO@+|EkLv*OD=OqyybOZ4=XRgc`wQvs1vh)TJkCE*)cZPvUj oSCpXmdiJ<{9 literal 63401 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�A_ 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 0) { diff --git a/eventmesh-common/src/main/java/org/apache/eventmesh/common/config/CommonConfiguration.java b/eventmesh-common/src/main/java/org/apache/eventmesh/common/config/CommonConfiguration.java index 08a44cb8e1..1ae6b2604a 100644 --- a/eventmesh-common/src/main/java/org/apache/eventmesh/common/config/CommonConfiguration.java +++ b/eventmesh-common/src/main/java/org/apache/eventmesh/common/config/CommonConfiguration.java @@ -17,17 +17,10 @@ package org.apache.eventmesh.common.config; -import java.net.Inet6Address; -import java.net.InetAddress; -import java.net.NetworkInterface; -import java.net.SocketException; -import java.net.UnknownHostException; -import java.util.ArrayList; -import java.util.Enumeration; - import com.google.common.base.Preconditions; import org.apache.commons.lang3.StringUtils; +import org.apache.eventmesh.common.IPUtil; public class CommonConfiguration { public String eventMeshEnv = "P"; @@ -35,7 +28,7 @@ public class CommonConfiguration { public String eventMeshCluster = "LS"; public String eventMeshName = ""; public String sysID = "5477"; - + public String eventMeshConnectorPluginType = "rocketmq"; public String namesrvAddr = ""; public String clientUserName = "username"; @@ -84,8 +77,11 @@ public void init() { eventMeshServerIp = configurationWraper.getProp(ConfKeys.KEYS_EVENTMESH_SERVER_HOST_IP); if (StringUtils.isBlank(eventMeshServerIp)) { - eventMeshServerIp = getLocalAddr(); + eventMeshServerIp = IPUtil.getLocalAddress(); } + + eventMeshConnectorPluginType = configurationWraper.getProp(ConfKeys.KEYS_ENENTMESH_CONNECTOR_PLUGIN_TYPE); + Preconditions.checkState(StringUtils.isNotEmpty(eventMeshConnectorPluginType), String.format("%s error", ConfKeys.KEYS_ENENTMESH_CONNECTOR_PLUGIN_TYPE)); } } @@ -105,94 +101,7 @@ static class ConfKeys { public static String KEYS_EVENTMESH_SERVER_REGISTER_INTERVAL = "eventMesh.server.registry.registerIntervalInMills"; public static String KEYS_EVENTMESH_SERVER_FETCH_REGISTRY_ADDR_INTERVAL = "eventMesh.server.registry.fetchRegistryAddrIntervalInMills"; - } - - public static String getLocalAddr() { - //priority of networkInterface when generating client ip - String priority = System.getProperty("networkInterface.priority", "bond1 preferList = new ArrayList(); - for (String eth : priority.split("<")) { - preferList.add(eth); - } - NetworkInterface preferNetworkInterface = null; - - try { - Enumeration enumeration1 = NetworkInterface.getNetworkInterfaces(); - while (enumeration1.hasMoreElements()) { - final NetworkInterface networkInterface = enumeration1.nextElement(); - if (!preferList.contains(networkInterface.getName())) { - continue; - } else if (preferNetworkInterface == null) { - preferNetworkInterface = networkInterface; - } - //get the networkInterface that has higher priority - else if (preferList.indexOf(networkInterface.getName()) - > preferList.indexOf(preferNetworkInterface.getName())) { - preferNetworkInterface = networkInterface; - } - } - - // Traversal Network interface to get the first non-loopback and non-private address - ArrayList ipv4Result = new ArrayList(); - ArrayList ipv6Result = new ArrayList(); - - if (preferNetworkInterface != null) { - final Enumeration en = preferNetworkInterface.getInetAddresses(); - getIpResult(ipv4Result, ipv6Result, en); - } else { - Enumeration enumeration = NetworkInterface.getNetworkInterfaces(); - while (enumeration.hasMoreElements()) { - final NetworkInterface networkInterface = enumeration.nextElement(); - final Enumeration en = networkInterface.getInetAddresses(); - getIpResult(ipv4Result, ipv6Result, en); - } - } - - // prefer ipv4 - if (!ipv4Result.isEmpty()) { - for (String ip : ipv4Result) { - if (ip.startsWith("127.0") || ip.startsWith("192.168")) { - continue; - } - - return ip; - } - return ipv4Result.get(ipv4Result.size() - 1); - } else if (!ipv6Result.isEmpty()) { - return ipv6Result.get(0); - } - //If failed to find,fall back to localhost - final InetAddress localHost = InetAddress.getLocalHost(); - return normalizeHostAddress(localHost); - } catch (SocketException e) { - e.printStackTrace(); - } catch (UnknownHostException e) { - e.printStackTrace(); - } - - return null; - } - - public static String normalizeHostAddress(final InetAddress localHost) { - if (localHost instanceof Inet6Address) { - return "[" + localHost.getHostAddress() + "]"; - } else { - return localHost.getHostAddress(); - } - } - - private static void getIpResult(ArrayList ipv4Result, ArrayList ipv6Result, - Enumeration en) { - while (en.hasMoreElements()) { - final InetAddress address = en.nextElement(); - if (!address.isLoopbackAddress()) { - if (address instanceof Inet6Address) { - ipv6Result.add(normalizeHostAddress(address)); - } else { - ipv4Result.add(normalizeHostAddress(address)); - } - } - } + public static String KEYS_ENENTMESH_CONNECTOR_PLUGIN_TYPE = "eventMesh.connector.plugin.type"; } } \ No newline at end of file diff --git a/eventmesh-common/src/main/java/org/apache/eventmesh/common/loadbalance/RandomLoadBalanceSelector.java b/eventmesh-common/src/main/java/org/apache/eventmesh/common/loadbalance/RandomLoadBalanceSelector.java index fc741a5a82..fb7a992237 100644 --- a/eventmesh-common/src/main/java/org/apache/eventmesh/common/loadbalance/RandomLoadBalanceSelector.java +++ b/eventmesh-common/src/main/java/org/apache/eventmesh/common/loadbalance/RandomLoadBalanceSelector.java @@ -28,7 +28,7 @@ * This selector use random strategy. * Each selection will randomly give one from the given list * - * @param + * @param Target type */ public class RandomLoadBalanceSelector implements LoadBalanceSelector { diff --git a/eventmesh-common/src/main/java/org/apache/eventmesh/common/loadbalance/WeightRoundRobinLoadBalanceSelector.java b/eventmesh-common/src/main/java/org/apache/eventmesh/common/loadbalance/WeightRoundRobinLoadBalanceSelector.java index d6f2009bf3..c0310003ad 100644 --- a/eventmesh-common/src/main/java/org/apache/eventmesh/common/loadbalance/WeightRoundRobinLoadBalanceSelector.java +++ b/eventmesh-common/src/main/java/org/apache/eventmesh/common/loadbalance/WeightRoundRobinLoadBalanceSelector.java @@ -27,7 +27,7 @@ * This selector use the weighted round robin strategy to select from list. * If the weight is greater, the probability of being selected is larger. * - * @param + * @param Target type */ public class WeightRoundRobinLoadBalanceSelector implements LoadBalanceSelector { diff --git a/eventmesh-common/src/test/java/org/apache/eventmesh/common/LiteMessageTest.java b/eventmesh-common/src/test/java/org/apache/eventmesh/common/LiteMessageTest.java new file mode 100644 index 0000000000..2f206a8d82 --- /dev/null +++ b/eventmesh-common/src/test/java/org/apache/eventmesh/common/LiteMessageTest.java @@ -0,0 +1,74 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.eventmesh.common; + +import org.junit.Assert; +import org.junit.Test; + +import java.util.HashMap; +import java.util.Map; + +public class LiteMessageTest { + + @Test + public void testGetProp() { + LiteMessage message = createLiteMessage(); + Assert.assertEquals(2L, message.getProp().size()); + } + + @Test + public void testSetProp() { + LiteMessage message = createLiteMessage(); + Map prop = new HashMap<>(); + prop.put("key3", "value3"); + message.setProp(prop); + Assert.assertEquals(1L, message.getProp().size()); + Assert.assertEquals("value3", message.getPropKey("key3")); + } + + @Test + public void testAddProp() { + LiteMessage message = createLiteMessage(); + message.addProp("key3", "value3"); + Assert.assertEquals(3L, message.getProp().size()); + Assert.assertEquals("value1", message.getPropKey("key1")); + } + + @Test + public void testGetPropKey() { + LiteMessage message = createLiteMessage(); + Assert.assertEquals("value1", message.getPropKey("key1")); + } + + @Test + public void testRemoveProp() { + LiteMessage message = createLiteMessage(); + message.removeProp("key1"); + Assert.assertEquals(1L, message.getProp().size()); + Assert.assertNull(message.getPropKey("key1")); + } + + private LiteMessage createLiteMessage() { + LiteMessage result = new LiteMessage(); + Map prop = new HashMap<>(); + prop.put("key1", "value1"); + prop.put("key2", "value2"); + result.setProp(prop); + return result; + } +} diff --git a/eventmesh-common/src/test/java/org/apache/eventmesh/common/command/HttpCommandTest.java b/eventmesh-common/src/test/java/org/apache/eventmesh/common/command/HttpCommandTest.java new file mode 100644 index 0000000000..ce5ac0c80b --- /dev/null +++ b/eventmesh-common/src/test/java/org/apache/eventmesh/common/command/HttpCommandTest.java @@ -0,0 +1,99 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.eventmesh.common.command; + +import io.netty.handler.codec.http.DefaultFullHttpResponse; +import io.netty.handler.codec.http.HttpHeaderNames; +import org.apache.eventmesh.common.protocol.http.body.BaseResponseBody; +import org.apache.eventmesh.common.protocol.http.body.Body; +import org.apache.eventmesh.common.protocol.http.header.Header; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnitRunner; + +import java.util.HashMap; +import java.util.Map; + +import static org.hamcrest.CoreMatchers.is; +import static org.mockito.Mockito.when; + +@RunWith(MockitoJUnitRunner.class) +public class HttpCommandTest { + + @Mock + private Header header; + + @Mock + private Body body; + + private HttpCommand httpCommand; + + @Before + public void before() { + httpCommand = new HttpCommand("POST", "1.1", "200"); + } + + @Test + public void testCreateHttpCommandResponseWithHeaderAndBody() { + HttpCommand command = httpCommand.createHttpCommandResponse(header, body); + Map headerMap = new HashMap<>(); + headerMap.put("key1", "value1"); + when(header.toMap()).thenReturn(headerMap); + Assert.assertEquals("1.1", command.getHttpVersion()); + Assert.assertEquals("POST", command.getHttpMethod()); + Assert.assertEquals("200", command.getRequestCode()); + Assert.assertEquals("value1", command.getHeader().toMap().get("key1")); + } + + @Test + public void testCreateHttpCommandResponseWithRetCodeAndRetMsg() { + HttpCommand command = httpCommand.createHttpCommandResponse(200, "SUCCESS"); + Assert.assertThat(((BaseResponseBody) command.getBody()).getRetCode(), is(200)); + Assert.assertEquals("SUCCESS", ((BaseResponseBody) command.getBody()).getRetMsg()); + } + + @Test + public void testAbstractDesc() { + HttpCommand command = httpCommand.createHttpCommandResponse(header, body); + String desc = command.abstractDesc(); + Assert.assertTrue(desc.startsWith("httpCommand")); + } + + @Test + public void testSimpleDesc() { + HttpCommand command = httpCommand.createHttpCommandResponse(header, body); + String desc = command.simpleDesc(); + Assert.assertTrue(desc.startsWith("httpCommand")); + } + + @Test + public void testHttpResponse() throws Exception { + HttpCommand command = httpCommand.createHttpCommandResponse(header, body); + DefaultFullHttpResponse response = command.httpResponse(); + Assert.assertEquals("keep-alive", response.headers().get(HttpHeaderNames.CONNECTION)); + } + + @Test + public void testHttpResponseWithREQCmdType() throws Exception { + DefaultFullHttpResponse response = httpCommand.httpResponse(); + Assert.assertNull(response); + } +} diff --git a/eventmesh-common/src/test/java/org/apache/eventmesh/common/config/CommonConfigurationTest.java b/eventmesh-common/src/test/java/org/apache/eventmesh/common/config/CommonConfigurationTest.java new file mode 100644 index 0000000000..7880cddb00 --- /dev/null +++ b/eventmesh-common/src/test/java/org/apache/eventmesh/common/config/CommonConfigurationTest.java @@ -0,0 +1,42 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.eventmesh.common.config; + +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; + +public class CommonConfigurationTest { + + private CommonConfiguration configuration; + + @Before + public void before() { + String file = ConfigurationWraperTest.class.getResource("/configuration.properties").getFile(); + ConfigurationWraper wraper = new ConfigurationWraper(file, false); + configuration = new CommonConfiguration(wraper); + } + + @Test + public void testInit() { + configuration.init(); + Assert.assertEquals("value1", configuration.eventMeshEnv); + Assert.assertEquals("value2", configuration.eventMeshIDC); + Assert.assertEquals("3", configuration.sysID); + } +} diff --git a/eventmesh-common/src/test/java/org/apache/eventmesh/common/config/ConfigurationWraperTest.java b/eventmesh-common/src/test/java/org/apache/eventmesh/common/config/ConfigurationWraperTest.java new file mode 100644 index 0000000000..7a89efa6ab --- /dev/null +++ b/eventmesh-common/src/test/java/org/apache/eventmesh/common/config/ConfigurationWraperTest.java @@ -0,0 +1,39 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.eventmesh.common.config; + +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; + +public class ConfigurationWraperTest { + + private ConfigurationWraper wraper; + + @Before + public void before() { + String file = ConfigurationWraperTest.class.getResource("/configuration.properties").getFile(); + wraper = new ConfigurationWraper(file, false); + } + + @Test + public void testGetProp() { + Assert.assertEquals("value1", wraper.getProp("eventMesh.server.env")); + Assert.assertEquals("value2", wraper.getProp("eventMesh.server.idc")); + } +} diff --git a/eventmesh-common/src/test/java/org/apache/eventmesh/common/loadbalance/WeightTest.java b/eventmesh-common/src/test/java/org/apache/eventmesh/common/loadbalance/WeightTest.java new file mode 100644 index 0000000000..bdf7a845f2 --- /dev/null +++ b/eventmesh-common/src/test/java/org/apache/eventmesh/common/loadbalance/WeightTest.java @@ -0,0 +1,44 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.eventmesh.common.loadbalance; + +import org.junit.Assert; +import org.junit.Test; + +public class WeightTest { + + @Test + public void testDecreaseTotal() { + Weight weight = new Weight(null, 0); + weight.decreaseTotal(1); + Assert.assertEquals(-1, weight.getCurrentWeight().get()); + } + + @Test + public void testIncreaseCurrentWeight() { + Weight weight = new Weight(null, 10); + weight.increaseCurrentWeight(); + Assert.assertEquals(10, weight.getCurrentWeight().get()); + } + + @Test + public void testGetCurrentWeight() { + Weight weight = new Weight(null, 0); + Assert.assertEquals(0, weight.getCurrentWeight().get()); + } +} diff --git a/eventmesh-common/src/test/java/org/apache/eventmesh/common/protocol/http/body/BaseResponseBodyTest.java b/eventmesh-common/src/test/java/org/apache/eventmesh/common/protocol/http/body/BaseResponseBodyTest.java new file mode 100644 index 0000000000..edba782483 --- /dev/null +++ b/eventmesh-common/src/test/java/org/apache/eventmesh/common/protocol/http/body/BaseResponseBodyTest.java @@ -0,0 +1,39 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.eventmesh.common.protocol.http.body; + +import org.apache.eventmesh.common.protocol.http.common.ProtocolKey; +import org.junit.Assert; +import org.junit.Test; + +import static org.hamcrest.CoreMatchers.is; + +public class BaseResponseBodyTest { + + @Test + public void testToMap() { + BaseResponseBody body = new BaseResponseBody(); + body.setRetCode(200); + body.setRetMsg("SUCCESS"); + Assert.assertTrue(body.toMap().containsKey(ProtocolKey.RETCODE)); + Assert.assertTrue(body.toMap().containsKey(ProtocolKey.RETMSG)); + Assert.assertTrue(body.toMap().containsKey(ProtocolKey.RESTIME)); + Assert.assertThat(body.toMap().get(ProtocolKey.RETCODE), is(200)); + Assert.assertThat(body.toMap().get(ProtocolKey.RETMSG), is("SUCCESS")); + } +} diff --git a/eventmesh-common/src/test/java/org/apache/eventmesh/common/protocol/http/header/BaseRequestHeaderTest.java b/eventmesh-common/src/test/java/org/apache/eventmesh/common/protocol/http/header/BaseRequestHeaderTest.java new file mode 100644 index 0000000000..e511f192cb --- /dev/null +++ b/eventmesh-common/src/test/java/org/apache/eventmesh/common/protocol/http/header/BaseRequestHeaderTest.java @@ -0,0 +1,40 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.eventmesh.common.protocol.http.header; + + +import org.apache.eventmesh.common.protocol.http.common.ProtocolKey; +import org.junit.Assert; +import org.junit.Test; + +import java.util.HashMap; +import java.util.Map; + +import static org.hamcrest.CoreMatchers.is; + +public class BaseRequestHeaderTest { + + @Test + public void testToMap() { + Map headerParam = new HashMap<>(); + headerParam.put(ProtocolKey.REQUEST_CODE, "200"); + BaseRequestHeader header = BaseRequestHeader.buildHeader(headerParam); + Assert.assertTrue(header.toMap().containsKey(ProtocolKey.REQUEST_CODE)); + Assert.assertThat(header.toMap().get(ProtocolKey.REQUEST_CODE), is("200")); + } +} diff --git a/eventmesh-common/src/test/java/org/apache/eventmesh/common/protocol/http/header/BaseResponseHeaderTest.java b/eventmesh-common/src/test/java/org/apache/eventmesh/common/protocol/http/header/BaseResponseHeaderTest.java new file mode 100644 index 0000000000..f963eea55e --- /dev/null +++ b/eventmesh-common/src/test/java/org/apache/eventmesh/common/protocol/http/header/BaseResponseHeaderTest.java @@ -0,0 +1,34 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.eventmesh.common.protocol.http.header; + +import org.apache.eventmesh.common.protocol.http.common.ProtocolKey; +import org.junit.Assert; +import org.junit.Test; + +import static org.hamcrest.CoreMatchers.is; + +public class BaseResponseHeaderTest { + + @Test + public void testToMap() { + BaseResponseHeader header = BaseResponseHeader.buildHeader("200"); + Assert.assertTrue(header.toMap().containsKey(ProtocolKey.REQUEST_CODE)); + Assert.assertThat(header.toMap().get(ProtocolKey.REQUEST_CODE), is("200")); + } +} diff --git a/eventmesh-common/src/test/java/org/apache/eventmesh/common/protocol/http/header/client/AbstractRequestHeaderTest.java b/eventmesh-common/src/test/java/org/apache/eventmesh/common/protocol/http/header/client/AbstractRequestHeaderTest.java new file mode 100644 index 0000000000..115aa12eea --- /dev/null +++ b/eventmesh-common/src/test/java/org/apache/eventmesh/common/protocol/http/header/client/AbstractRequestHeaderTest.java @@ -0,0 +1,38 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.eventmesh.common.protocol.http.header.client; + +import org.apache.eventmesh.common.protocol.http.common.ProtocolKey; +import org.apache.eventmesh.common.protocol.http.header.Header; +import org.junit.Assert; + +public class AbstractRequestHeaderTest { + + public void assertMapContent(Header header) { + Assert.assertTrue(header.toMap().containsKey(ProtocolKey.REQUEST_CODE)); + Assert.assertTrue(header.toMap().containsKey(ProtocolKey.LANGUAGE)); + Assert.assertTrue(header.toMap().containsKey(ProtocolKey.VERSION)); + Assert.assertTrue(header.toMap().containsKey(ProtocolKey.ClientInstanceKey.ENV)); + Assert.assertTrue(header.toMap().containsKey(ProtocolKey.ClientInstanceKey.IDC)); + Assert.assertTrue(header.toMap().containsKey(ProtocolKey.ClientInstanceKey.SYS)); + Assert.assertTrue(header.toMap().containsKey(ProtocolKey.ClientInstanceKey.PID)); + Assert.assertTrue(header.toMap().containsKey(ProtocolKey.ClientInstanceKey.IP)); + Assert.assertTrue(header.toMap().containsKey(ProtocolKey.ClientInstanceKey.USERNAME)); + Assert.assertTrue(header.toMap().containsKey(ProtocolKey.ClientInstanceKey.PASSWD)); + } +} diff --git a/eventmesh-common/src/test/java/org/apache/eventmesh/common/protocol/http/header/client/AbstractResponseHeaderTest.java b/eventmesh-common/src/test/java/org/apache/eventmesh/common/protocol/http/header/client/AbstractResponseHeaderTest.java new file mode 100644 index 0000000000..0b5b0bda92 --- /dev/null +++ b/eventmesh-common/src/test/java/org/apache/eventmesh/common/protocol/http/header/client/AbstractResponseHeaderTest.java @@ -0,0 +1,40 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.eventmesh.common.protocol.http.header.client; + +import org.apache.eventmesh.common.protocol.http.common.ProtocolKey; +import org.apache.eventmesh.common.protocol.http.header.Header; +import org.junit.Assert; + +import static org.hamcrest.CoreMatchers.is; + +public class AbstractResponseHeaderTest { + + public void assertMapContent(Header header) { + Assert.assertTrue(header.toMap().containsKey(ProtocolKey.REQUEST_CODE)); + Assert.assertTrue(header.toMap().containsKey(ProtocolKey.EventMeshInstanceKey.EVENTMESHCLUSTER)); + Assert.assertTrue(header.toMap().containsKey(ProtocolKey.EventMeshInstanceKey.EVENTMESHIP)); + Assert.assertTrue(header.toMap().containsKey(ProtocolKey.EventMeshInstanceKey.EVENTMESHENV)); + Assert.assertTrue(header.toMap().containsKey(ProtocolKey.EventMeshInstanceKey.EVENTMESHIDC)); + Assert.assertThat(header.toMap().get(ProtocolKey.REQUEST_CODE), is(200)); + Assert.assertThat(header.toMap().get(ProtocolKey.EventMeshInstanceKey.EVENTMESHCLUSTER), is("CLUSTER")); + Assert.assertThat(header.toMap().get(ProtocolKey.EventMeshInstanceKey.EVENTMESHIP), is("127.0.0.1")); + Assert.assertThat(header.toMap().get(ProtocolKey.EventMeshInstanceKey.EVENTMESHENV), is("DEV")); + Assert.assertThat(header.toMap().get(ProtocolKey.EventMeshInstanceKey.EVENTMESHIDC), is("IDC")); + } +} diff --git a/eventmesh-common/src/test/java/org/apache/eventmesh/common/protocol/http/header/client/HeartbeatRequestHeaderTest.java b/eventmesh-common/src/test/java/org/apache/eventmesh/common/protocol/http/header/client/HeartbeatRequestHeaderTest.java new file mode 100644 index 0000000000..7b1afaeea0 --- /dev/null +++ b/eventmesh-common/src/test/java/org/apache/eventmesh/common/protocol/http/header/client/HeartbeatRequestHeaderTest.java @@ -0,0 +1,32 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.eventmesh.common.protocol.http.header.client; + + +import org.junit.Test; + +import java.util.HashMap; + +public class HeartbeatRequestHeaderTest extends AbstractRequestHeaderTest { + + @Test + public void testToMap() { + HeartbeatRequestHeader header = HeartbeatRequestHeader.buildHeader(new HashMap<>()); + assertMapContent(header); + } +} diff --git a/eventmesh-common/src/test/java/org/apache/eventmesh/common/protocol/http/header/client/HeartbeatResponseHeaderTest.java b/eventmesh-common/src/test/java/org/apache/eventmesh/common/protocol/http/header/client/HeartbeatResponseHeaderTest.java new file mode 100644 index 0000000000..d27aeaf8d4 --- /dev/null +++ b/eventmesh-common/src/test/java/org/apache/eventmesh/common/protocol/http/header/client/HeartbeatResponseHeaderTest.java @@ -0,0 +1,34 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.eventmesh.common.protocol.http.header.client; + +import org.apache.eventmesh.common.protocol.http.common.ProtocolKey; +import org.junit.Assert; +import org.junit.Test; + +import static org.hamcrest.CoreMatchers.is; + +public class HeartbeatResponseHeaderTest extends AbstractResponseHeaderTest { + + @Test + public void testToMap() { + HeartbeatResponseHeader header = HeartbeatResponseHeader.buildHeader(200, + "CLUSTER", "127.0.0.1", "DEV", "IDC"); + assertMapContent(header); + } +} diff --git a/eventmesh-common/src/test/java/org/apache/eventmesh/common/protocol/http/header/client/RegRequestHeaderTest.java b/eventmesh-common/src/test/java/org/apache/eventmesh/common/protocol/http/header/client/RegRequestHeaderTest.java new file mode 100644 index 0000000000..dbeb33bf26 --- /dev/null +++ b/eventmesh-common/src/test/java/org/apache/eventmesh/common/protocol/http/header/client/RegRequestHeaderTest.java @@ -0,0 +1,31 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.eventmesh.common.protocol.http.header.client; + +import org.junit.Test; + +import java.util.HashMap; + +public class RegRequestHeaderTest extends AbstractRequestHeaderTest { + + @Test + public void testToMap() { + RegRequestHeader header = RegRequestHeader.buildHeader(new HashMap<>()); + assertMapContent(header); + } +} diff --git a/eventmesh-common/src/test/java/org/apache/eventmesh/common/protocol/http/header/client/RegResponseHeaderTest.java b/eventmesh-common/src/test/java/org/apache/eventmesh/common/protocol/http/header/client/RegResponseHeaderTest.java new file mode 100644 index 0000000000..503c6425ed --- /dev/null +++ b/eventmesh-common/src/test/java/org/apache/eventmesh/common/protocol/http/header/client/RegResponseHeaderTest.java @@ -0,0 +1,30 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.eventmesh.common.protocol.http.header.client; + +import org.junit.Test; + +public class RegResponseHeaderTest extends AbstractResponseHeaderTest { + + @Test + public void testToMap() { + RegResponseHeader header = RegResponseHeader.buildHeader(200, + "CLUSTER", "127.0.0.1", "DEV", "IDC"); + assertMapContent(header); + } +} diff --git a/eventmesh-common/src/test/java/org/apache/eventmesh/common/protocol/http/header/client/SubscribeRequestHeaderTest.java b/eventmesh-common/src/test/java/org/apache/eventmesh/common/protocol/http/header/client/SubscribeRequestHeaderTest.java new file mode 100644 index 0000000000..e883688903 --- /dev/null +++ b/eventmesh-common/src/test/java/org/apache/eventmesh/common/protocol/http/header/client/SubscribeRequestHeaderTest.java @@ -0,0 +1,31 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.eventmesh.common.protocol.http.header.client; + +import org.junit.Test; + +import java.util.HashMap; + +public class SubscribeRequestHeaderTest extends AbstractRequestHeaderTest { + + @Test + public void testToMap() { + SubscribeRequestHeader header = SubscribeRequestHeader.buildHeader(new HashMap<>()); + assertMapContent(header); + } +} diff --git a/eventmesh-common/src/test/java/org/apache/eventmesh/common/protocol/http/header/client/SubscribeResponseHeaderTest.java b/eventmesh-common/src/test/java/org/apache/eventmesh/common/protocol/http/header/client/SubscribeResponseHeaderTest.java new file mode 100644 index 0000000000..9d646338dd --- /dev/null +++ b/eventmesh-common/src/test/java/org/apache/eventmesh/common/protocol/http/header/client/SubscribeResponseHeaderTest.java @@ -0,0 +1,31 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.eventmesh.common.protocol.http.header.client; + + +import org.junit.Test; + +public class SubscribeResponseHeaderTest extends AbstractResponseHeaderTest { + + @Test + public void testToMap() { + SubscribeResponseHeader header = SubscribeResponseHeader.buildHeader(200, + "CLUSTER", "127.0.0.1", "DEV", "IDC"); + assertMapContent(header); + } +} diff --git a/eventmesh-common/src/test/java/org/apache/eventmesh/common/protocol/http/header/client/UnRegRequestHeaderTest.java b/eventmesh-common/src/test/java/org/apache/eventmesh/common/protocol/http/header/client/UnRegRequestHeaderTest.java new file mode 100644 index 0000000000..9e77a21711 --- /dev/null +++ b/eventmesh-common/src/test/java/org/apache/eventmesh/common/protocol/http/header/client/UnRegRequestHeaderTest.java @@ -0,0 +1,32 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.eventmesh.common.protocol.http.header.client; + + +import org.junit.Test; + +import java.util.HashMap; + +public class UnRegRequestHeaderTest extends AbstractRequestHeaderTest { + + @Test + public void testToMap() { + UnRegRequestHeader header = UnRegRequestHeader.buildHeader(new HashMap<>()); + assertMapContent(header); + } +} diff --git a/eventmesh-common/src/test/java/org/apache/eventmesh/common/protocol/http/header/client/UnRegResponseHeaderTest.java b/eventmesh-common/src/test/java/org/apache/eventmesh/common/protocol/http/header/client/UnRegResponseHeaderTest.java new file mode 100644 index 0000000000..0c7b016417 --- /dev/null +++ b/eventmesh-common/src/test/java/org/apache/eventmesh/common/protocol/http/header/client/UnRegResponseHeaderTest.java @@ -0,0 +1,30 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.eventmesh.common.protocol.http.header.client; + +import org.junit.Test; + +public class UnRegResponseHeaderTest extends AbstractResponseHeaderTest { + + @Test + public void testToMap() { + UnRegResponseHeader header = UnRegResponseHeader.buildHeader(200, + "CLUSTER", "127.0.0.1", "DEV", "IDC"); + assertMapContent(header); + } +} diff --git a/eventmesh-common/src/test/java/org/apache/eventmesh/common/protocol/http/header/client/UnSubscribeRequestHeaderTest.java b/eventmesh-common/src/test/java/org/apache/eventmesh/common/protocol/http/header/client/UnSubscribeRequestHeaderTest.java new file mode 100644 index 0000000000..5ee7c9e509 --- /dev/null +++ b/eventmesh-common/src/test/java/org/apache/eventmesh/common/protocol/http/header/client/UnSubscribeRequestHeaderTest.java @@ -0,0 +1,31 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.eventmesh.common.protocol.http.header.client; + +import org.junit.Test; + +import java.util.HashMap; + +public class UnSubscribeRequestHeaderTest extends AbstractRequestHeaderTest { + + @Test + public void testToMap() { + UnSubscribeRequestHeader header = UnSubscribeRequestHeader.buildHeader(new HashMap<>()); + assertMapContent(header); + } +} diff --git a/eventmesh-common/src/test/java/org/apache/eventmesh/common/protocol/http/header/client/UnSubscribeResponseHeaderTest.java b/eventmesh-common/src/test/java/org/apache/eventmesh/common/protocol/http/header/client/UnSubscribeResponseHeaderTest.java new file mode 100644 index 0000000000..3e8cb70ed4 --- /dev/null +++ b/eventmesh-common/src/test/java/org/apache/eventmesh/common/protocol/http/header/client/UnSubscribeResponseHeaderTest.java @@ -0,0 +1,31 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.eventmesh.common.protocol.http.header.client; + + +import org.junit.Test; + +public class UnSubscribeResponseHeaderTest extends AbstractResponseHeaderTest { + + @Test + public void testToMap() { + UnSubscribeResponseHeader header = UnSubscribeResponseHeader.buildHeader(200, + "CLUSTER", "127.0.0.1", "DEV", "IDC"); + assertMapContent(header); + } +} diff --git a/eventmesh-common/src/test/resources/configuration.properties b/eventmesh-common/src/test/resources/configuration.properties new file mode 100644 index 0000000000..76f29f2771 --- /dev/null +++ b/eventmesh-common/src/test/resources/configuration.properties @@ -0,0 +1,24 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +eventMesh.server.env=value1 +eventMesh.server.idc=value2 +eventMesh.sysid=3 +eventMesh.server.cluster=value4 +eventMesh.server.name=value5 +eventMesh.server.hostIp=value6 +eventMesh.connector.plugin.type=rocketmq diff --git a/eventmesh-connector-api/build.gradle b/eventmesh-connector-api/build.gradle index 2d1205df38..157048ecc4 100644 --- a/eventmesh-connector-api/build.gradle +++ b/eventmesh-connector-api/build.gradle @@ -20,6 +20,6 @@ List open_message = [ ] dependencies { - implementation open_message,project(":eventmesh-common") - testImplementation open_message,project(":eventmesh-common") + implementation open_message,project(":eventmesh-common"), project(":eventmesh-spi") + testImplementation open_message,project(":eventmesh-common"), project(":eventmesh-spi") } diff --git a/eventmesh-connector-api/gradle.properties b/eventmesh-connector-api/gradle.properties index ae30087cf9..9d1744e07a 100644 --- a/eventmesh-connector-api/gradle.properties +++ b/eventmesh-connector-api/gradle.properties @@ -16,6 +16,6 @@ # group=org.apache.eventmesh version=1.2.0-SNAPSHOT -jdk=1.7 +jdk=1.8 mavenUserName= mavenPassword= \ No newline at end of file diff --git a/eventmesh-connector-api/src/main/java/org/apache/eventmesh/api/consumer/MeshMQPushConsumer.java b/eventmesh-connector-api/src/main/java/org/apache/eventmesh/api/consumer/MeshMQPushConsumer.java index 5e60e0e0df..4ac1edbfc8 100644 --- a/eventmesh-connector-api/src/main/java/org/apache/eventmesh/api/consumer/MeshMQPushConsumer.java +++ b/eventmesh-connector-api/src/main/java/org/apache/eventmesh/api/consumer/MeshMQPushConsumer.java @@ -25,7 +25,9 @@ import io.openmessaging.api.Message; import org.apache.eventmesh.api.AbstractContext; +import org.apache.eventmesh.spi.EventMeshSPI; +@EventMeshSPI public interface MeshMQPushConsumer extends Consumer { void init(Properties keyValue) throws Exception; diff --git a/eventmesh-connector-api/src/main/java/org/apache/eventmesh/api/producer/MeshMQProducer.java b/eventmesh-connector-api/src/main/java/org/apache/eventmesh/api/producer/MeshMQProducer.java index 82ca583ce7..c717385e05 100644 --- a/eventmesh-connector-api/src/main/java/org/apache/eventmesh/api/producer/MeshMQProducer.java +++ b/eventmesh-connector-api/src/main/java/org/apache/eventmesh/api/producer/MeshMQProducer.java @@ -24,7 +24,9 @@ import io.openmessaging.api.SendCallback; import org.apache.eventmesh.api.RRCallback; +import org.apache.eventmesh.spi.EventMeshSPI; +@EventMeshSPI public interface MeshMQProducer extends Producer { void init(Properties properties) throws Exception; diff --git a/eventmesh-connector-rocketmq/src/main/java/org/apache/eventmesh/connector/rocketmq/config/ClientConfiguration.java b/eventmesh-connector-rocketmq/src/main/java/org/apache/eventmesh/connector/rocketmq/config/ClientConfiguration.java index 11a1858a6e..6fa41042b6 100644 --- a/eventmesh-connector-rocketmq/src/main/java/org/apache/eventmesh/connector/rocketmq/config/ClientConfiguration.java +++ b/eventmesh-connector-rocketmq/src/main/java/org/apache/eventmesh/connector/rocketmq/config/ClientConfiguration.java @@ -33,7 +33,7 @@ public class ClientConfiguration { public Integer ackWindow = 1000; public Integer pubWindow = 100; public long consumeTimeout = 0L; - public Integer pollNameServerInteval = 10 * 1000; + public Integer pollNameServerInterval = 10 * 1000; public Integer heartbeatBrokerInterval = 30 * 1000; public Integer rebalanceInterval = 20 * 1000; @@ -104,18 +104,18 @@ public void init() { String clientPollNamesrvIntervalStr = configurationWraper.getProp(ConfKeys.KEYS_EVENTMESH_ROCKETMQ_CLIENT_POLL_NAMESRV_INTERVAL); if (StringUtils.isNotEmpty(clientPollNamesrvIntervalStr)) { Preconditions.checkState(StringUtils.isNumeric(clientPollNamesrvIntervalStr), String.format("%s error", ConfKeys.KEYS_EVENTMESH_ROCKETMQ_CLIENT_POLL_NAMESRV_INTERVAL)); - pollNameServerInteval = Integer.valueOf(clientPollNamesrvIntervalStr); + pollNameServerInterval = Integer.valueOf(clientPollNamesrvIntervalStr); } - String clientHeartbeatBrokerIntervalStr = configurationWraper.getProp(ConfKeys.KEYS_EVENTMESH_ROCKETMQ_CLIENT_HEARTBEAT_BROKER_INTERVEL); + String clientHeartbeatBrokerIntervalStr = configurationWraper.getProp(ConfKeys.KEYS_EVENTMESH_ROCKETMQ_CLIENT_HEARTBEAT_BROKER_INTERVAL); if (StringUtils.isNotEmpty(clientHeartbeatBrokerIntervalStr)) { - Preconditions.checkState(StringUtils.isNumeric(clientHeartbeatBrokerIntervalStr), String.format("%s error", ConfKeys.KEYS_EVENTMESH_ROCKETMQ_CLIENT_HEARTBEAT_BROKER_INTERVEL)); + Preconditions.checkState(StringUtils.isNumeric(clientHeartbeatBrokerIntervalStr), String.format("%s error", ConfKeys.KEYS_EVENTMESH_ROCKETMQ_CLIENT_HEARTBEAT_BROKER_INTERVAL)); heartbeatBrokerInterval = Integer.valueOf(clientHeartbeatBrokerIntervalStr); } - String clientRebalanceIntervalIntervalStr = configurationWraper.getProp(ConfKeys.KEYS_EVENTMESH_ROCKETMQ_CLIENT_REBALANCE_INTERVEL); + String clientRebalanceIntervalIntervalStr = configurationWraper.getProp(ConfKeys.KEYS_EVENTMESH_ROCKETMQ_CLIENT_REBALANCE_INTERVAL); if (StringUtils.isNotEmpty(clientRebalanceIntervalIntervalStr)) { - Preconditions.checkState(StringUtils.isNumeric(clientRebalanceIntervalIntervalStr), String.format("%s error", ConfKeys.KEYS_EVENTMESH_ROCKETMQ_CLIENT_REBALANCE_INTERVEL)); + Preconditions.checkState(StringUtils.isNumeric(clientRebalanceIntervalIntervalStr), String.format("%s error", ConfKeys.KEYS_EVENTMESH_ROCKETMQ_CLIENT_REBALANCE_INTERVAL)); rebalanceInterval = Integer.valueOf(clientRebalanceIntervalIntervalStr); } } @@ -144,9 +144,9 @@ static class ConfKeys { public static String KEYS_EVENTMESH_ROCKETMQ_CLIENT_POLL_NAMESRV_INTERVAL = "eventMesh.server.rocketmq.client.pollNameServerInterval"; - public static String KEYS_EVENTMESH_ROCKETMQ_CLIENT_HEARTBEAT_BROKER_INTERVEL = "eventMesh.server.rocketmq.client.heartbeatBrokerInterval"; + public static String KEYS_EVENTMESH_ROCKETMQ_CLIENT_HEARTBEAT_BROKER_INTERVAL = "eventMesh.server.rocketmq.client.heartbeatBrokerInterval"; - public static String KEYS_EVENTMESH_ROCKETMQ_CLIENT_REBALANCE_INTERVEL = "eventMesh.server.rocketmq.client.rebalanceInterval"; + public static String KEYS_EVENTMESH_ROCKETMQ_CLIENT_REBALANCE_INTERVAL = "eventMesh.server.rocketmq.client.rebalanceInterval"; } } \ No newline at end of file diff --git a/eventmesh-connector-rocketmq/src/main/java/org/apache/eventmesh/connector/rocketmq/utils/OMSUtil.java b/eventmesh-connector-rocketmq/src/main/java/org/apache/eventmesh/connector/rocketmq/utils/OMSUtil.java index ba35acf186..906be5f239 100644 --- a/eventmesh-connector-rocketmq/src/main/java/org/apache/eventmesh/connector/rocketmq/utils/OMSUtil.java +++ b/eventmesh-connector-rocketmq/src/main/java/org/apache/eventmesh/connector/rocketmq/utils/OMSUtil.java @@ -221,6 +221,9 @@ public static boolean isOMSHeader(String value) { /** * Convert a RocketMQ SEND_OK SendResult instance to a OMS SendResult. + * + * @param rmqResult RocketMQ result + * @return send result */ public static SendResult sendResultConvert(org.apache.rocketmq.client.producer.SendResult rmqResult) { SendResult sendResult = new SendResult(); @@ -241,6 +244,9 @@ public static SendResult sendResultConvert(org.apache.rocketmq.client.producer.S /** * Returns an iterator that cycles indefinitely over the elements of {@code Iterable}. + * + * @param Target type + * @return Iterator */ public static Iterator cycle(final Iterable iterable) { return new Iterator() { diff --git a/eventmesh-connector-rocketmq/src/main/resources/META-INF/eventmesh/org.apache.eventmesh.api.consumer.MeshMQPushConsumer b/eventmesh-connector-rocketmq/src/main/resources/META-INF/eventmesh/org.apache.eventmesh.api.consumer.MeshMQPushConsumer new file mode 100644 index 0000000000..0df2e286d7 --- /dev/null +++ b/eventmesh-connector-rocketmq/src/main/resources/META-INF/eventmesh/org.apache.eventmesh.api.consumer.MeshMQPushConsumer @@ -0,0 +1,16 @@ +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +rocketmq=org.apache.eventmesh.connector.rocketmq.consumer.RocketMQConsumerImpl \ No newline at end of file diff --git a/eventmesh-connector-rocketmq/src/main/resources/META-INF/eventmesh/org.apache.eventmesh.api.producer.MeshMQProducer b/eventmesh-connector-rocketmq/src/main/resources/META-INF/eventmesh/org.apache.eventmesh.api.producer.MeshMQProducer new file mode 100644 index 0000000000..ef4959d994 --- /dev/null +++ b/eventmesh-connector-rocketmq/src/main/resources/META-INF/eventmesh/org.apache.eventmesh.api.producer.MeshMQProducer @@ -0,0 +1,16 @@ +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +rocketmq=org.apache.eventmesh.connector.rocketmq.producer.RocketMQProducerImpl \ No newline at end of file diff --git a/eventmesh-runtime/build.gradle b/eventmesh-runtime/build.gradle index 09dd79f9c2..e5bb065659 100644 --- a/eventmesh-runtime/build.gradle +++ b/eventmesh-runtime/build.gradle @@ -31,6 +31,6 @@ List open_message = [ dependencies { - implementation metrics, open_message,project(":eventmesh-connector-api"),project(":eventmesh-common") - testImplementation metrics,open_message,project(":eventmesh-common"),project(":eventmesh-connector-api") + implementation metrics, open_message,project(":eventmesh-connector-api"),project(":eventmesh-common"),project(":eventmesh-spi") + testImplementation metrics,open_message,project(":eventmesh-common"),project(":eventmesh-connector-api"),project(":eventmesh-spi") } diff --git a/eventmesh-runtime/conf/eventmesh.properties b/eventmesh-runtime/conf/eventmesh.properties index 035b950fc6..45fc193b4f 100644 --- a/eventmesh-runtime/conf/eventmesh.properties +++ b/eventmesh-runtime/conf/eventmesh.properties @@ -51,4 +51,7 @@ eventMesh.server.admin.http.port=10106 eventMesh.server.registry.registerIntervalInMills=10000 eventMesh.server.registry.fetchRegistryAddrIntervalInMills=20000 #auto-ack -#eventMesh.server.defibus.client.comsumeTimeoutInMin=5 \ No newline at end of file +#eventMesh.server.defibus.client.comsumeTimeoutInMin=5 + +#connector plugin +eventMesh.connector.plugin.type=rocketmq \ No newline at end of file diff --git a/eventmesh-runtime/src/main/java/org/apache/eventmesh/runtime/configuration/EventMeshHTTPConfiguration.java b/eventmesh-runtime/src/main/java/org/apache/eventmesh/runtime/configuration/EventMeshHTTPConfiguration.java index a73c24a7b7..55e6896a5e 100644 --- a/eventmesh-runtime/src/main/java/org/apache/eventmesh/runtime/configuration/EventMeshHTTPConfiguration.java +++ b/eventmesh-runtime/src/main/java/org/apache/eventmesh/runtime/configuration/EventMeshHTTPConfiguration.java @@ -48,7 +48,7 @@ public class EventMeshHTTPConfiguration extends CommonConfiguration { public int eventMeshServerRetryThreadNum = 2; - public int eventMeshServerPullRegistryIntervel = 30000; + public int eventMeshServerPullRegistryInterval = 30000; public int eventMeshServerAsyncAccumulationThreshold = 1000; @@ -62,7 +62,7 @@ public class EventMeshHTTPConfiguration extends CommonConfiguration { public int eventMeshServerClientManageBlockQSize = 1000; - public int eventMeshServerBusyCheckIntervel = 1000; + public int eventMeshServerBusyCheckInterval = 1000; public boolean eventMeshServerConsumerEnabled = false; @@ -126,9 +126,9 @@ public void init() { eventMeshServerClientManageThreadNum = Integer.valueOf(StringUtils.deleteWhitespace(eventMeshServerClientManageThreadNumStr)); } - String eventMeshServerPullRegistryIntervelStr = configurationWraper.getProp(ConfKeys.KEYS_EVENTMESH_PULL_REGISTRY_INTERVEL); - if (StringUtils.isNotEmpty(eventMeshServerPullRegistryIntervelStr) && StringUtils.isNumeric(eventMeshServerPullRegistryIntervelStr)) { - eventMeshServerPullRegistryIntervel = Integer.valueOf(StringUtils.deleteWhitespace(eventMeshServerPullRegistryIntervelStr)); + String eventMeshServerPullRegistryIntervalStr = configurationWraper.getProp(ConfKeys.KEYS_EVENTMESH_PULL_REGISTRY_INTERVAL); + if (StringUtils.isNotEmpty(eventMeshServerPullRegistryIntervalStr) && StringUtils.isNumeric(eventMeshServerPullRegistryIntervalStr)) { + eventMeshServerPullRegistryInterval = Integer.valueOf(StringUtils.deleteWhitespace(eventMeshServerPullRegistryIntervalStr)); } String eventMeshServerAdminThreadNumStr = configurationWraper.getProp(ConfKeys.KEYS_EVENTMESH_ADMIN_THREAD_NUM); @@ -161,9 +161,9 @@ public void init() { eventMeshServerClientManageBlockQSize = Integer.valueOf(StringUtils.deleteWhitespace(eventMeshServerClientManageBlockQSizeStr)); } - String eventMeshServerBusyCheckIntervelStr = configurationWraper.getProp(ConfKeys.KEY_EVENTMESH_BUSY_CHECK_INTERVEL); - if (StringUtils.isNotEmpty(eventMeshServerBusyCheckIntervelStr) && StringUtils.isNumeric(eventMeshServerBusyCheckIntervelStr)) { - eventMeshServerBusyCheckIntervel = Integer.valueOf(StringUtils.deleteWhitespace(eventMeshServerBusyCheckIntervelStr)); + String eventMeshServerBusyCheckIntervalStr = configurationWraper.getProp(ConfKeys.KEY_EVENTMESH_BUSY_CHECK_INTERVAL); + if (StringUtils.isNotEmpty(eventMeshServerBusyCheckIntervalStr) && StringUtils.isNumeric(eventMeshServerBusyCheckIntervalStr)) { + eventMeshServerBusyCheckInterval = Integer.valueOf(StringUtils.deleteWhitespace(eventMeshServerBusyCheckIntervalStr)); } String eventMeshServerConsumerEnabledStr = configurationWraper.getProp(ConfKeys.KEY_EVENTMESH_CONSUMER_ENABLED); @@ -195,7 +195,7 @@ static class ConfKeys { public static String KEYS_EVENTMESH_ASYNC_ACCUMULATION_THRESHOLD = "eventMesh.server.async.accumulation.threshold"; - public static String KEY_EVENTMESH_BUSY_CHECK_INTERVEL = "eventMesh.server.busy.check.intervel"; + public static String KEY_EVENTMESH_BUSY_CHECK_INTERVAL = "eventMesh.server.busy.check.interval"; public static String KEYS_EVENTMESH_SENDMSG_THREAD_NUM = "eventMesh.server.sendmsg.threads.num"; @@ -211,7 +211,7 @@ static class ConfKeys { public static String KEY_EVENTMESH_RETRY_THREAD_NUM = "eventMesh.server.retry.threads.num"; - public static String KEYS_EVENTMESH_PULL_REGISTRY_INTERVEL = "eventMesh.server.pull.registry.intervel"; + public static String KEYS_EVENTMESH_PULL_REGISTRY_INTERVAL = "eventMesh.server.pull.registry.interval"; public static String KEY_EVENTMESH_RETRY_BLOCKQ_SIZE = "eventMesh.server.retry.blockQ.size"; diff --git a/eventmesh-runtime/src/main/java/org/apache/eventmesh/runtime/core/plugin/MQConsumerWrapper.java b/eventmesh-runtime/src/main/java/org/apache/eventmesh/runtime/core/plugin/MQConsumerWrapper.java index 080b7af40a..b09f9eda11 100644 --- a/eventmesh-runtime/src/main/java/org/apache/eventmesh/runtime/core/plugin/MQConsumerWrapper.java +++ b/eventmesh-runtime/src/main/java/org/apache/eventmesh/runtime/core/plugin/MQConsumerWrapper.java @@ -35,6 +35,14 @@ public class MQConsumerWrapper extends MQWrapper { protected MeshMQPushConsumer meshMQPushConsumer; + public MQConsumerWrapper(String connectorPluginType) { + this.meshMQPushConsumer = PluginFactory.getMeshMQPushConsumer(connectorPluginType); + if (meshMQPushConsumer == null) { + logger.error("can't load the meshMQPushConsumer plugin, please check."); + throw new RuntimeException("doesn't load the meshMQPushConsumer plugin, please check."); + } + } + public void subscribe(String topic, AsyncMessageListener listener) throws Exception { meshMQPushConsumer.subscribe(topic, listener); } diff --git a/eventmesh-runtime/src/main/java/org/apache/eventmesh/runtime/core/plugin/MQProducerWrapper.java b/eventmesh-runtime/src/main/java/org/apache/eventmesh/runtime/core/plugin/MQProducerWrapper.java index 082ab3bc00..03c38a73fa 100644 --- a/eventmesh-runtime/src/main/java/org/apache/eventmesh/runtime/core/plugin/MQProducerWrapper.java +++ b/eventmesh-runtime/src/main/java/org/apache/eventmesh/runtime/core/plugin/MQProducerWrapper.java @@ -34,6 +34,14 @@ public class MQProducerWrapper extends MQWrapper { protected MeshMQProducer meshMQProducer; + public MQProducerWrapper(String connectorPluginType) { + this.meshMQProducer = PluginFactory.getMeshMQProducer(connectorPluginType); + if (meshMQProducer == null) { + logger.error("can't load the meshMQProducer plugin, please check."); + throw new RuntimeException("doesn't load the meshMQProducer plugin, please check."); + } + } + public synchronized void init(Properties keyValue) throws Exception { if (inited.get()) { return; @@ -44,8 +52,8 @@ public synchronized void init(Properties keyValue) throws Exception { logger.error("can't load the meshMQProducer plugin, please check."); throw new RuntimeException("doesn't load the meshMQProducer plugin, please check."); } - meshMQProducer.init(keyValue); + meshMQProducer.init(keyValue); inited.compareAndSet(false, true); } diff --git a/eventmesh-runtime/src/main/java/org/apache/eventmesh/runtime/core/plugin/PluginFactory.java b/eventmesh-runtime/src/main/java/org/apache/eventmesh/runtime/core/plugin/PluginFactory.java new file mode 100644 index 0000000000..b11495341b --- /dev/null +++ b/eventmesh-runtime/src/main/java/org/apache/eventmesh/runtime/core/plugin/PluginFactory.java @@ -0,0 +1,39 @@ +/* + * Licensed to Apache Software Foundation (ASF) under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Apache Software Foundation (ASF) licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.eventmesh.runtime.core.plugin; + +import org.apache.eventmesh.api.consumer.MeshMQPushConsumer; +import org.apache.eventmesh.api.producer.MeshMQProducer; +import org.apache.eventmesh.spi.EventMeshExtensionFactory; + +public class PluginFactory { + + public static MeshMQProducer getMeshMQProducer(String connectorPluginName) { + return EventMeshExtensionFactory.getExtension(MeshMQProducer.class, connectorPluginName); + } + + public static MeshMQPushConsumer getMeshMQPushConsumer(String connectorPluginName) { + return EventMeshExtensionFactory.getExtension(MeshMQPushConsumer.class, connectorPluginName); + } + + private static T getPlugin(Class pluginType, String pluginName) { + return EventMeshExtensionFactory.getExtension(pluginType, pluginName); + } +} diff --git a/eventmesh-runtime/src/main/java/org/apache/eventmesh/runtime/core/protocol/http/consumer/EventMeshConsumer.java b/eventmesh-runtime/src/main/java/org/apache/eventmesh/runtime/core/protocol/http/consumer/EventMeshConsumer.java index 8620e682ab..ef051b03f4 100644 --- a/eventmesh-runtime/src/main/java/org/apache/eventmesh/runtime/core/protocol/http/consumer/EventMeshConsumer.java +++ b/eventmesh-runtime/src/main/java/org/apache/eventmesh/runtime/core/protocol/http/consumer/EventMeshConsumer.java @@ -66,13 +66,15 @@ public class EventMeshConsumer { private ConsumerGroupConf consumerGroupConf; - private MQConsumerWrapper persistentMqConsumer = new MQConsumerWrapper(); + private MQConsumerWrapper persistentMqConsumer; - private MQConsumerWrapper broadcastMqConsumer = new MQConsumerWrapper(); + private MQConsumerWrapper broadcastMqConsumer; public EventMeshConsumer(EventMeshHTTPServer eventMeshHTTPServer, ConsumerGroupConf consumerGroupConf) { this.eventMeshHTTPServer = eventMeshHTTPServer; this.consumerGroupConf = consumerGroupConf; + this.persistentMqConsumer = new MQConsumerWrapper(eventMeshHTTPServer.getEventMeshHttpConfiguration().eventMeshConnectorPluginType); + this.broadcastMqConsumer = new MQConsumerWrapper(eventMeshHTTPServer.getEventMeshHttpConfiguration().eventMeshConnectorPluginType); } private MessageHandler httpMessageHandler; diff --git a/eventmesh-runtime/src/main/java/org/apache/eventmesh/runtime/core/protocol/http/producer/EventMeshProducer.java b/eventmesh-runtime/src/main/java/org/apache/eventmesh/runtime/core/protocol/http/producer/EventMeshProducer.java index cf41ca2b9e..fe32180879 100644 --- a/eventmesh-runtime/src/main/java/org/apache/eventmesh/runtime/core/protocol/http/producer/EventMeshProducer.java +++ b/eventmesh-runtime/src/main/java/org/apache/eventmesh/runtime/core/protocol/http/producer/EventMeshProducer.java @@ -69,7 +69,7 @@ public boolean reply(final SendMessageContext sendMsgContext, final SendCallback return true; } - protected MQProducerWrapper mqProducerWrapper = new MQProducerWrapper(); + protected MQProducerWrapper mqProducerWrapper; public MQProducerWrapper getMqProducerWrapper() { return mqProducerWrapper; @@ -85,7 +85,7 @@ public synchronized void init(EventMeshHTTPConfiguration eventMeshHttpConfigurat //TODO for defibus keyValue.put("eventMeshIDC", eventMeshHttpConfiguration.eventMeshIDC); - + mqProducerWrapper = new MQProducerWrapper(eventMeshHttpConfiguration.eventMeshConnectorPluginType); mqProducerWrapper.init(keyValue); inited.compareAndSet(false, true); logger.info("EventMeshProducer [{}] inited.............", producerGroupConfig.getGroupName()); diff --git a/eventmesh-runtime/src/main/java/org/apache/eventmesh/runtime/core/protocol/tcp/client/group/ClientGroupWrapper.java b/eventmesh-runtime/src/main/java/org/apache/eventmesh/runtime/core/protocol/tcp/client/group/ClientGroupWrapper.java index 5829e4941a..310ea6dee2 100644 --- a/eventmesh-runtime/src/main/java/org/apache/eventmesh/runtime/core/protocol/tcp/client/group/ClientGroupWrapper.java +++ b/eventmesh-runtime/src/main/java/org/apache/eventmesh/runtime/core/protocol/tcp/client/group/ClientGroupWrapper.java @@ -50,6 +50,7 @@ import org.apache.eventmesh.runtime.constants.EventMeshConstants; import org.apache.eventmesh.runtime.core.plugin.MQConsumerWrapper; import org.apache.eventmesh.runtime.core.plugin.MQProducerWrapper; +import org.apache.eventmesh.runtime.core.plugin.PluginFactory; import org.apache.eventmesh.runtime.core.protocol.tcp.client.group.dispatch.DownstreamDispatchStrategy; import org.apache.eventmesh.runtime.core.protocol.tcp.client.session.Session; import org.apache.eventmesh.runtime.core.protocol.tcp.client.session.push.DownStreamMsgContext; @@ -96,14 +97,16 @@ public class ClientGroupWrapper { public AtomicBoolean inited4Broadcast = new AtomicBoolean(Boolean.FALSE); - private MQConsumerWrapper persistentMsgConsumer = new MQConsumerWrapper(); + private MQConsumerWrapper persistentMsgConsumer; - private MQConsumerWrapper broadCastMsgConsumer = new MQConsumerWrapper(); + private MQConsumerWrapper broadCastMsgConsumer; private ConcurrentHashMap> topic2sessionInGroupMapping = new ConcurrentHashMap>(); public AtomicBoolean producerStarted = new AtomicBoolean(Boolean.FALSE); + private MQProducerWrapper mqProducerWrapper; + public ClientGroupWrapper(String sysId, String producerGroup, String consumerGroup, EventMeshTCPServer eventMeshTCPServer, DownstreamDispatchStrategy downstreamDispatchStrategy) { @@ -115,6 +118,9 @@ public ClientGroupWrapper(String sysId, String producerGroup, String consumerGro this.eventMeshTcpRetryer = eventMeshTCPServer.getEventMeshTcpRetryer(); this.eventMeshTcpMonitor = eventMeshTCPServer.getEventMeshTcpMonitor(); this.downstreamDispatchStrategy = downstreamDispatchStrategy; + this.persistentMsgConsumer = new MQConsumerWrapper(eventMeshTCPServer.getEventMeshTCPConfiguration().eventMeshConnectorPluginType); + this.broadCastMsgConsumer = new MQConsumerWrapper(eventMeshTCPServer.getEventMeshTCPConfiguration().eventMeshConnectorPluginType); + this.mqProducerWrapper = new MQProducerWrapper(eventMeshTCPServer.getEventMeshTCPConfiguration().eventMeshConnectorPluginType); } public ConcurrentHashMap> getTopic2sessionInGroupMapping() { @@ -163,8 +169,6 @@ public void onException(OnExceptionContext context) { return true; } - private MQProducerWrapper mqProducerWrapper = new MQProducerWrapper(); - public MQProducerWrapper getMqProducerWrapper() { return mqProducerWrapper; } diff --git a/eventmesh-sdk-java/src/main/java/org/apache/eventmesh/client/http/consumer/LiteConsumer.java b/eventmesh-sdk-java/src/main/java/org/apache/eventmesh/client/http/consumer/LiteConsumer.java index 7841a67eaa..61d3c487f1 100644 --- a/eventmesh-sdk-java/src/main/java/org/apache/eventmesh/client/http/consumer/LiteConsumer.java +++ b/eventmesh-sdk-java/src/main/java/org/apache/eventmesh/client/http/consumer/LiteConsumer.java @@ -96,7 +96,7 @@ public LiteConsumer(LiteClientConfig liteClientConfig, // this.remotingServer = new RemotingServer(this.consumeExecutor); } - private AtomicBoolean started = new AtomicBoolean(Boolean.FALSE); + private final AtomicBoolean started = new AtomicBoolean(Boolean.FALSE); @Override public void start() throws Exception { @@ -226,15 +226,14 @@ public void run() { EventMeshRetObj ret = JSON.parseObject(res, EventMeshRetObj.class); - if (ret.getRetCode() == EventMeshRetCode.SUCCESS.getRetCode()) { - } else { + if (ret.getRetCode() != EventMeshRetCode.SUCCESS.getRetCode()) { throw new EventMeshException(ret.getRetCode(), ret.getRetMsg()); } } catch (Exception e) { logger.error("send heartBeat error", e); } } - }, EventMeshCommon.HEATBEAT, EventMeshCommon.HEATBEAT, TimeUnit.MILLISECONDS); + }, EventMeshCommon.HEARTBEAT, EventMeshCommon.HEARTBEAT, TimeUnit.MILLISECONDS); } public boolean unsubscribe(List topicList, String url) throws Exception { diff --git a/eventmesh-sdk-java/src/main/java/org/apache/eventmesh/client/tcp/common/EventMeshCommon.java b/eventmesh-sdk-java/src/main/java/org/apache/eventmesh/client/tcp/common/EventMeshCommon.java index 65aa40e918..04c275d3d5 100644 --- a/eventmesh-sdk-java/src/main/java/org/apache/eventmesh/client/tcp/common/EventMeshCommon.java +++ b/eventmesh-sdk-java/src/main/java/org/apache/eventmesh/client/tcp/common/EventMeshCommon.java @@ -31,7 +31,7 @@ public class EventMeshCommon { /** * CLIENT端心跳间隔时间 */ - public static int HEATBEAT = 30 * 1000; + public static int HEARTBEAT = 30 * 1000; /** * RR 废弃清理的时间间隔 diff --git a/eventmesh-sdk-java/src/main/java/org/apache/eventmesh/client/tcp/common/TcpClient.java b/eventmesh-sdk-java/src/main/java/org/apache/eventmesh/client/tcp/common/TcpClient.java index 7df1e29f30..5513e1310e 100644 --- a/eventmesh-sdk-java/src/main/java/org/apache/eventmesh/client/tcp/common/TcpClient.java +++ b/eventmesh-sdk-java/src/main/java/org/apache/eventmesh/client/tcp/common/TcpClient.java @@ -50,7 +50,7 @@ import org.slf4j.LoggerFactory; public abstract class TcpClient implements Closeable { - private Logger logger = LoggerFactory.getLogger(this.getClass()); + private final Logger logger = LoggerFactory.getLogger(this.getClass()); public int clientNo = (new Random()).nextInt(1000); @@ -67,8 +67,6 @@ public abstract class TcpClient implements Closeable { protected static final ScheduledThreadPoolExecutor scheduler = new ScheduledThreadPoolExecutor(4, new EventMeshThreadFactoryImpl("TCPClientScheduler", true)); - private ScheduledFuture task; - public TcpClient(String host, int port) { this.host = host; this.port = port; @@ -119,7 +117,7 @@ protected void send(Package msg) throws Exception { if (channel.isWritable()) { channel.writeAndFlush(msg).addListener((ChannelFutureListener) future -> { if (!future.isSuccess()) { - logger.warn("send msg failed", future.isSuccess(), future.cause()); + logger.warn("send msg failed", future.cause()); } }); } else { diff --git a/eventmesh-sdk-java/src/main/java/org/apache/eventmesh/client/tcp/impl/SimplePubClientImpl.java b/eventmesh-sdk-java/src/main/java/org/apache/eventmesh/client/tcp/impl/SimplePubClientImpl.java index da5691a79f..df05f4bb61 100644 --- a/eventmesh-sdk-java/src/main/java/org/apache/eventmesh/client/tcp/impl/SimplePubClientImpl.java +++ b/eventmesh-sdk-java/src/main/java/org/apache/eventmesh/client/tcp/impl/SimplePubClientImpl.java @@ -41,7 +41,7 @@ public class SimplePubClientImpl extends TcpClient implements SimplePubClient { - private Logger logger = LoggerFactory.getLogger(this.getClass()); + private final Logger logger = LoggerFactory.getLogger(this.getClass()); private UserAgent userAgent; @@ -90,10 +90,10 @@ public void run() { } Package msg = MessageUtils.heartBeat(); io(msg, EventMeshCommon.DEFAULT_TIME_OUT_MILLS); - } catch (Exception e) { + } catch (Exception ignore) { } } - }, EventMeshCommon.HEATBEAT, EventMeshCommon.HEATBEAT, TimeUnit.MILLISECONDS); + }, EventMeshCommon.HEARTBEAT, EventMeshCommon.HEARTBEAT, TimeUnit.MILLISECONDS); } private void goodbye() throws Exception { @@ -176,16 +176,13 @@ protected void channelRead0(ChannelHandlerContext ctx, Package msg) throws Excep Package pkg = MessageUtils.responseToClientAck(msg); send(pkg); } else if (cmd == Command.SERVER_GOODBYE_REQUEST) { - + //TODO } RequestContext context = contexts.get(RequestContext._key(msg)); if (context != null) { contexts.remove(context.getKey()); context.finish(msg); - return; - } else { - return; } } } diff --git a/eventmesh-sdk-java/src/main/java/org/apache/eventmesh/client/tcp/impl/SimpleSubClientImpl.java b/eventmesh-sdk-java/src/main/java/org/apache/eventmesh/client/tcp/impl/SimpleSubClientImpl.java index 38d52f653d..7e341cadd4 100644 --- a/eventmesh-sdk-java/src/main/java/org/apache/eventmesh/client/tcp/impl/SimpleSubClientImpl.java +++ b/eventmesh-sdk-java/src/main/java/org/apache/eventmesh/client/tcp/impl/SimpleSubClientImpl.java @@ -44,7 +44,7 @@ public class SimpleSubClientImpl extends TcpClient implements SimpleSubClient { - private Logger logger = LoggerFactory.getLogger(this.getClass()); + private final Logger logger = LoggerFactory.getLogger(this.getClass()); private UserAgent userAgent; @@ -101,10 +101,10 @@ public void run() { } Package msg = MessageUtils.heartBeat(); SimpleSubClientImpl.this.io(msg, EventMeshCommon.DEFAULT_TIME_OUT_MILLS); - } catch (Exception e) { + } catch (Exception ignore) { } } - }, EventMeshCommon.HEATBEAT, EventMeshCommon.HEATBEAT, TimeUnit.MILLISECONDS); + }, EventMeshCommon.HEARTBEAT, EventMeshCommon.HEARTBEAT, TimeUnit.MILLISECONDS); } private void goodbye() throws Exception { @@ -172,10 +172,8 @@ protected void channelRead0(ChannelHandlerContext ctx, Package msg) throws Excep if (context != null) { contexts.remove(context.getKey()); context.finish(msg); - return; } else { logger.error("msg ignored,context not found.|{}|{}", cmd, msg); - return; } } } diff --git a/eventmesh-spi/build.gradle b/eventmesh-spi/build.gradle new file mode 100644 index 0000000000..d973dcedae --- /dev/null +++ b/eventmesh-spi/build.gradle @@ -0,0 +1,16 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ \ No newline at end of file diff --git a/eventmesh-spi/gradle.properties b/eventmesh-spi/gradle.properties new file mode 100644 index 0000000000..d0503c3b7b --- /dev/null +++ b/eventmesh-spi/gradle.properties @@ -0,0 +1,20 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +group=org.apache.eventmesh +version=1.2.0-SNAPSHOT +jdk=1.8 +snapshot=false diff --git a/eventmesh-spi/src/main/java/org/apache/eventmesh/spi/EventMeshExtensionFactory.java b/eventmesh-spi/src/main/java/org/apache/eventmesh/spi/EventMeshExtensionFactory.java new file mode 100644 index 0000000000..6aea9db11d --- /dev/null +++ b/eventmesh-spi/src/main/java/org/apache/eventmesh/spi/EventMeshExtensionFactory.java @@ -0,0 +1,38 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.eventmesh.spi; + +import org.apache.commons.lang3.StringUtils; +import org.slf4j.Logger; + +public enum EventMeshExtensionFactory { + ; + + public static T getExtension(Class extensionType, String extensionName) { + if (extensionType == null) { + throw new ExtensionException("extensionType is null"); + } + if (StringUtils.isEmpty(extensionName)) { + throw new ExtensionException("extensionName is null"); + } + if (!extensionType.isInterface() || !extensionType.isAnnotationPresent(EventMeshSPI.class)) { + throw new ExtensionException(String.format("extensionType:%s is invalided", extensionType)); + } + return EventMeshExtensionLoader.getExtension(extensionType, extensionName); + } +} diff --git a/eventmesh-spi/src/main/java/org/apache/eventmesh/spi/EventMeshExtensionLoader.java b/eventmesh-spi/src/main/java/org/apache/eventmesh/spi/EventMeshExtensionLoader.java new file mode 100644 index 0000000000..89696e04da --- /dev/null +++ b/eventmesh-spi/src/main/java/org/apache/eventmesh/spi/EventMeshExtensionLoader.java @@ -0,0 +1,118 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.eventmesh.spi; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.IOException; +import java.io.InputStream; +import java.net.URL; +import java.util.Enumeration; +import java.util.Properties; +import java.util.concurrent.ConcurrentHashMap; + +public enum EventMeshExtensionLoader { + ; + + private static final Logger logger = LoggerFactory.getLogger(EventMeshExtensionLoader.class); + + private static final ConcurrentHashMap, ConcurrentHashMap>> EXTENSION_CLASS_LOAD_CACHE = new ConcurrentHashMap<>(16); + + private static final ConcurrentHashMap EXTENSION_INSTANCE_CACHE = new ConcurrentHashMap<>(16); + + private static final String EVENTMESH_EXTENSION_DIR = "META-INF/eventmesh/"; + + @SuppressWarnings("unchecked") + public static T getExtension(Class extensionType, String extensionName) { + if (!hasLoadExtensionClass(extensionType)) { + loadExtensionClass(extensionType); + } + if (!hasInitializeExtension(extensionName)) { + initializeExtension(extensionType, extensionName); + } + return (T) EXTENSION_INSTANCE_CACHE.get(extensionName); + } + + private static void initializeExtension(Class extensionType, String extensionName) { + ConcurrentHashMap> extensionClassMap = EXTENSION_CLASS_LOAD_CACHE.get(extensionType); + if (extensionClassMap == null) { + throw new ExtensionException(String.format("Extension type:%s has not been loaded", extensionType)); + } + if (!extensionClassMap.containsKey(extensionName)) { + throw new ExtensionException(String.format("Extension name:%s has not been loaded", extensionName)); + } + Class aClass = extensionClassMap.get(extensionName); + try { + Object extensionObj = aClass.newInstance(); + logger.info("initialize extension instance success, extensionType: {}, extensionName: {}", extensionType, extensionName); + EXTENSION_INSTANCE_CACHE.put(extensionName, extensionObj); + } catch (InstantiationException | IllegalAccessException e) { + throw new ExtensionException("Extension initialize error", e); + } + } + + public static void loadExtensionClass(Class extensionType) { + String extensionFileName = EVENTMESH_EXTENSION_DIR + extensionType.getName(); + ClassLoader classLoader = EventMeshExtensionLoader.class.getClassLoader(); + try { + Enumeration extensionUrls = classLoader.getResources(extensionFileName); + if (extensionUrls != null) { + while (extensionUrls.hasMoreElements()) { + URL url = extensionUrls.nextElement(); + loadResources(url, extensionType); + } + } + } catch (IOException e) { + throw new ExtensionException("load extension class error", e); + } + + + } + + private static void loadResources(URL url, Class extensionType) throws IOException { + try (InputStream inputStream = url.openStream()) { + Properties properties = new Properties(); + properties.load(inputStream); + properties.forEach((extensionName, extensionClass) -> { + String extensionNameStr = (String) extensionName; + String extensionClassStr = (String) extensionClass; + try { + Class targetClass = Class.forName(extensionClassStr); + logger.info("load extension class success, extensionType: {}, extensionClass: {}", extensionType, targetClass); + if (!extensionType.isAssignableFrom(targetClass)) { + throw new ExtensionException( + String.format("class: %s is not subClass of %s", targetClass, extensionType)); + } + EXTENSION_CLASS_LOAD_CACHE.computeIfAbsent(extensionType, k -> new ConcurrentHashMap<>()) + .put(extensionNameStr, targetClass); + } catch (ClassNotFoundException e) { + throw new ExtensionException("load extension class error", e); + } + }); + } + } + + private static boolean hasLoadExtensionClass(Class extensionType) { + return EXTENSION_CLASS_LOAD_CACHE.containsKey(extensionType); + } + + private static boolean hasInitializeExtension(String extensionName) { + return EXTENSION_INSTANCE_CACHE.containsKey(extensionName); + } +} diff --git a/eventmesh-spi/src/main/java/org/apache/eventmesh/spi/EventMeshSPI.java b/eventmesh-spi/src/main/java/org/apache/eventmesh/spi/EventMeshSPI.java new file mode 100644 index 0000000000..0ea72d431b --- /dev/null +++ b/eventmesh-spi/src/main/java/org/apache/eventmesh/spi/EventMeshSPI.java @@ -0,0 +1,35 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.eventmesh.spi; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Just as a marker for SPI + */ +@Documented +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.TYPE}) +public @interface EventMeshSPI { + +} + diff --git a/eventmesh-spi/src/main/java/org/apache/eventmesh/spi/ExtensionException.java b/eventmesh-spi/src/main/java/org/apache/eventmesh/spi/ExtensionException.java new file mode 100644 index 0000000000..874f03da5d --- /dev/null +++ b/eventmesh-spi/src/main/java/org/apache/eventmesh/spi/ExtensionException.java @@ -0,0 +1,33 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.eventmesh.spi; + +public class ExtensionException extends RuntimeException { + + public ExtensionException(Exception e) { + super(e); + } + + public ExtensionException(String message) { + super(message); + } + + public ExtensionException(String message, Exception e) { + super(message, e); + } +} diff --git a/eventmesh-spi/src/test/java/org/apache/eventmesh/spi/EventMeshExtensionFactoryTest.java b/eventmesh-spi/src/test/java/org/apache/eventmesh/spi/EventMeshExtensionFactoryTest.java new file mode 100644 index 0000000000..649f4b18b2 --- /dev/null +++ b/eventmesh-spi/src/test/java/org/apache/eventmesh/spi/EventMeshExtensionFactoryTest.java @@ -0,0 +1,29 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.eventmesh.spi; + +import org.junit.Test; + +public class EventMeshExtensionFactoryTest { + + @Test + public void getExtension() { + TestExtension extensionA = EventMeshExtensionFactory.getExtension(TestExtension.class, "extensionA"); + extensionA.hello(); + } +} \ No newline at end of file diff --git a/eventmesh-spi/src/test/java/org/apache/eventmesh/spi/ExtensionA.java b/eventmesh-spi/src/test/java/org/apache/eventmesh/spi/ExtensionA.java new file mode 100644 index 0000000000..03513e6203 --- /dev/null +++ b/eventmesh-spi/src/test/java/org/apache/eventmesh/spi/ExtensionA.java @@ -0,0 +1,26 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.eventmesh.spi; + +public class ExtensionA implements TestExtension { + + @Override + public void hello() { + System.out.println("I am ExtensionA"); + } +} diff --git a/eventmesh-spi/src/test/java/org/apache/eventmesh/spi/TestExtension.java b/eventmesh-spi/src/test/java/org/apache/eventmesh/spi/TestExtension.java new file mode 100644 index 0000000000..c0c9888130 --- /dev/null +++ b/eventmesh-spi/src/test/java/org/apache/eventmesh/spi/TestExtension.java @@ -0,0 +1,24 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.eventmesh.spi; + +@EventMeshSPI +public interface TestExtension { + + void hello(); +} diff --git a/eventmesh-spi/src/test/resources/META-INF/eventmesh/org.apache.eventmesh.spi.TestExtension b/eventmesh-spi/src/test/resources/META-INF/eventmesh/org.apache.eventmesh.spi.TestExtension new file mode 100644 index 0000000000..3862ccbb4f --- /dev/null +++ b/eventmesh-spi/src/test/resources/META-INF/eventmesh/org.apache.eventmesh.spi.TestExtension @@ -0,0 +1,17 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +extensionA=org.apache.eventmesh.spi.ExtensionA \ No newline at end of file diff --git a/eventmesh-test/src/main/java/org/apache/eventmesh/http/demo/SyncRequestInstance.java b/eventmesh-test/src/main/java/org/apache/eventmesh/http/demo/SyncRequestInstance.java index 9d3af8ffe4..329f2bc648 100644 --- a/eventmesh-test/src/main/java/org/apache/eventmesh/http/demo/SyncRequestInstance.java +++ b/eventmesh-test/src/main/java/org/apache/eventmesh/http/demo/SyncRequestInstance.java @@ -34,10 +34,15 @@ public class SyncRequestInstance { public static void main(String[] args) throws Exception { LiteProducer liteProducer = null; + String eventMeshIPPort = "127.0.0.1:10105"; + String topic = "EventMesh.SyncRequestInstance"; try { - String eventMeshIPPort = args[0]; - - final String topic = args[1]; + if (args.length > 0 && StringUtils.isNotBlank(args[0])) { + eventMeshIPPort = args[0]; + } + if (args.length > 1 && StringUtils.isNotBlank(args[1])) { + topic = args[1]; + } if (StringUtils.isBlank(eventMeshIPPort)) { // if has multi value, can config as: 127.0.0.1:10105;127.0.0.2:10105 diff --git a/settings.gradle b/settings.gradle index ea31ffdc16..2b5e0af662 100644 --- a/settings.gradle +++ b/settings.gradle @@ -17,5 +17,12 @@ rootProject.name = 'EventMesh' String jdkVersion = "${jdk}" -include 'eventmesh-runtime','eventmesh-connector-rocketmq','eventmesh-sdk-java','eventmesh-common','eventmesh-connector-api','eventmesh-starter','eventmesh-test' +include 'eventmesh-runtime' +include 'eventmesh-connector-rocketmq' +include 'eventmesh-sdk-java' +include 'eventmesh-common' +include 'eventmesh-connector-api' +include 'eventmesh-starter' +include 'eventmesh-test' +include 'eventmesh-spi'