Full Code of Cicizz/jmqtt for AI

master 032ac7eababf cached
139 files
414.7 KB
106.7k tokens
896 symbols
1 requests
Download .txt
Showing preview only (466K chars total). Download the full file or copy to clipboard to get everything.
Repository: Cicizz/jmqtt
Branch: master
Commit: 032ac7eababf
Files: 139
Total size: 414.7 KB

Directory structure:
gitextract_nylx23xb/

├── .dockerignore
├── .gitignore
├── Dockerfile
├── LICENSE
├── README.md
├── README_CN.md
├── jmqtt-acceptance/
│   ├── README.md
│   └── pom.xml
├── jmqtt-admin/
│   └── pom.xml
├── jmqtt-broker/
│   ├── pom.xml
│   └── src/
│       ├── main/
│       │   ├── java/
│       │   │   └── org/
│       │   │       └── jmqtt/
│       │   │           └── broker/
│       │   │               ├── BrokerController.java
│       │   │               └── BrokerStartup.java
│       │   └── resources/
│       │       ├── bin/
│       │       │   ├── jmqttshutdown
│       │       │   └── runbroker.sh
│       │       └── conf/
│       │           ├── client.cer
│       │           ├── jmqtt.properties
│       │           ├── jmqtt.sql
│       │           ├── log4j2.xml
│       │           └── server.pfx
│       └── test/
│           └── java/
│               └── org/
│                   └── jmqtt/
│                       └── broker/
│                           └── test/
│                               └── Runtime.java
├── jmqtt-bus/
│   ├── README.md
│   ├── pom.xml
│   └── src/
│       ├── main/
│       │   └── java/
│       │       └── org/
│       │           └── jmqtt/
│       │               └── bus/
│       │                   ├── Authenticator.java
│       │                   ├── BusController.java
│       │                   ├── ClusterEventManager.java
│       │                   ├── DeviceMessageManager.java
│       │                   ├── DeviceSessionManager.java
│       │                   ├── DeviceSubscriptionManager.java
│       │                   ├── enums/
│       │                   │   ├── ClusterEventCodeEnum.java
│       │                   │   ├── DeviceOnlineStateEnum.java
│       │                   │   ├── MessageAckEnum.java
│       │                   │   ├── MessageFlowEnum.java
│       │                   │   ├── MessageSourceEnum.java
│       │                   │   └── TransportProtocolEnum.java
│       │                   ├── event/
│       │                   │   ├── EventCenter.java
│       │                   │   └── GatewayListener.java
│       │                   ├── impl/
│       │                   │   ├── ClusterEventManagerImpl.java
│       │                   │   ├── DefaultAuthenticator.java
│       │                   │   ├── DeviceMessageManagerImpl.java
│       │                   │   ├── DeviceSessionManagerImpl.java
│       │                   │   └── DeviceSubscriptionManagerImpl.java
│       │                   ├── model/
│       │                   │   ├── ClusterEvent.java
│       │                   │   ├── DeviceInboxMessage.java
│       │                   │   ├── DeviceMessage.java
│       │                   │   ├── DeviceSession.java
│       │                   │   └── DeviceSubscription.java
│       │                   ├── store/
│       │                   │   ├── AbstractDBStore.java
│       │                   │   ├── DBCallback.java
│       │                   │   ├── DBUtils.java
│       │                   │   ├── daoobject/
│       │                   │   │   ├── DeviceInboxMessageDO.java
│       │                   │   │   ├── EventDO.java
│       │                   │   │   ├── MessageDO.java
│       │                   │   │   ├── RetainMessageDO.java
│       │                   │   │   ├── SessionDO.java
│       │                   │   │   └── SubscriptionDO.java
│       │                   │   └── mapper/
│       │                   │       ├── ClientInboxMessageMapper.java
│       │                   │       ├── EventMapper.java
│       │                   │       ├── MessageMapper.java
│       │                   │       ├── RetainMessageMapper.java
│       │                   │       ├── SessionMapper.java
│       │                   │       └── SubscriptionMapper.java
│       │                   └── subscription/
│       │                       ├── CNode.java
│       │                       ├── CTrie.java
│       │                       ├── CTrieSubscriptionMatcher.java
│       │                       ├── DefaultSubscriptionTreeMatcher.java
│       │                       ├── DumpTreeVisitor.java
│       │                       ├── INode.java
│       │                       ├── SubscriptionCounterVisitor.java
│       │                       ├── SubscriptionMatcher.java
│       │                       ├── TNode.java
│       │                       ├── Token.java
│       │                       ├── Topic.java
│       │                       └── model/
│       │                           └── Subscription.java
│       └── test/
│           └── java/
│               └── org/
│                   └── jmqtt/
│                       ├── AppTest.java
│                       └── bus/
│                           └── subscription/
│                               ├── CTrieSubscriptionMatcherMatchingTest.java
│                               ├── CTrieTest.java
│                               └── TopicTest.java
├── jmqtt-doc/
│   ├── README.md
│   ├── docs/
│   │   ├── README.md
│   │   └── TEST_REPORT.md
│   └── pom.xml
├── jmqtt-example/
│   ├── pom.xml
│   └── src/
│       └── main/
│           └── java/
│               └── org/
│                   └── jmqtt/
│                       ├── java/
│                       │   ├── Consumer.java
│                       │   └── Producer.java
│                       └── websocket/
│                           ├── paho-mqtt-min.js
│                           ├── paho-mqtt.js
│                           └── webSocket.html
├── jmqtt-manager
├── jmqtt-mqtt/
│   ├── README.md
│   ├── pom.xml
│   └── src/
│       └── main/
│           └── java/
│               └── org/
│                   └── jmqtt/
│                       └── mqtt/
│                           ├── ConnectManager.java
│                           ├── MQTTConnection.java
│                           ├── MQTTConnectionFactory.java
│                           ├── MQTTServer.java
│                           ├── codec/
│                           │   ├── ByteBuf2WebSocketEncoder.java
│                           │   └── WebSocket2ByteBufDecoder.java
│                           ├── event/
│                           │   └── MqttEventListener.java
│                           ├── model/
│                           │   └── MqttTopic.java
│                           ├── netty/
│                           │   ├── MqttNettyUtils.java
│                           │   ├── MqttRemotingServer.java
│                           │   ├── NettyMqttHandler.java
│                           │   └── NettySslHandler.java
│                           ├── protocol/
│                           │   ├── RequestProcessor.java
│                           │   └── impl/
│                           │       ├── ConnectProcessor.java
│                           │       ├── DisconnectProcessor.java
│                           │       ├── PingProcessor.java
│                           │       ├── PubAckProcessor.java
│                           │       ├── PubCompProcessor.java
│                           │       ├── PubRecProcessor.java
│                           │       ├── PubRelProcessor.java
│                           │       ├── PublishProcessor.java
│                           │       ├── SubscribeProcessor.java
│                           │       └── UnSubscribeProcessor.java
│                           ├── retain/
│                           │   ├── RetainMessageHandler.java
│                           │   └── impl/
│                           │       └── RetainMessageHandlerImpl.java
│                           ├── session/
│                           │   └── MqttSession.java
│                           └── utils/
│                               ├── MqttMessageUtil.java
│                               └── MqttMsgHeader.java
├── jmqtt-support/
│   ├── README.md
│   ├── pom.xml
│   └── src/
│       └── main/
│           └── java/
│               └── org/
│                   └── jmqtt/
│                       └── support/
│                           ├── config/
│                           │   ├── BrokerConfig.java
│                           │   └── NettyConfig.java
│                           ├── exception/
│                           │   ├── RemotingConnectException.java
│                           │   ├── RemotingException.java
│                           │   ├── RemotingSendRequestException.java
│                           │   ├── RemotingTimeoutException.java
│                           │   └── RemotingTooMuchRequestException.java
│                           ├── helper/
│                           │   ├── MixAll.java
│                           │   ├── Pair.java
│                           │   ├── RejectHandler.java
│                           │   └── ThreadFactoryImpl.java
│                           ├── log/
│                           │   ├── JmqttLogger.java
│                           │   └── LogUtil.java
│                           └── remoting/
│                               ├── RemotingHelper.java
│                               └── RemotingService.java
├── jmqtt-tcp/
│   ├── README.md
│   ├── pom.xml
│   └── src/
│       └── main/
│           └── java/
│               └── org/
│                   └── jmqtt/
│                       └── App.java
└── pom.xml

================================================
FILE CONTENTS
================================================

================================================
FILE: .dockerignore
================================================
rocksdb.db
.git
.env
*.jpg
*.png
*.md
**/target

================================================
FILE: .gitignore
================================================
storage/
run/
test_out/
tmp/
doc/
.directory
.loadpath
.metadata
*.pydevproject
*.bak
*.swp
*.tmp
*~.nib
local.properties
*/dependency-reduced-pom.xml


# Maven #
#########
target/

# Eclipse #
###########
.project
.classpath
.settings/
.externalToolBuilders/
*.launch
.recommenders/

# Intellij #
############
.idea/
*.iml
*.ipr
*.iws

# Compiled source #
###################
*.class
*.com
*.dll
*.exe
*.o
*.so


=======
# Compiled class file
*.class

# Log file
*.log

# BlueJ files
*.ctxt

# Mobile Tools for Java (J2ME)
.mtj.tmp/

# Package Files #
*.jar
*.war
*.nar
*.ear
*.zip
*.tar.gz
*.rar

# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
hs_err_pid*
rocksdb.db/


================================================
FILE: Dockerfile
================================================
# Builder
FROM maven:3-jdk-8-slim AS builder

RUN mkdir /opt/jmqtt
WORKDIR /opt/jmqtt

ADD . ./

RUN mvn -Ppackage-all -DskipTests clean install -U

# App
FROM openjdk:8-jre-slim

RUN mkdir /opt/jmqtt
VOLUME /var/jmqtt-data
WORKDIR /opt/jmqtt

COPY --from=builder /opt/jmqtt ./
COPY --from=builder /opt/jmqtt/jmqtt-distribution/conf /var/jmqtt-data/conf/


EXPOSE 1883
EXPOSE 1884

CMD ["./jmqtt-distribution/target/jmqtt/bin/jmqttstart", "-h", "/var/jmqtt-data", "-c", "/var/jmqtt-data/conf/jmqtt.properties"]


================================================
FILE: LICENSE
================================================
                                 Apache License
                           Version 2.0, January 2004
                        http://www.apache.org/licenses/

   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION

   1. Definitions.

      "License" shall mean the terms and conditions for use, reproduction,
      and distribution as defined by Sections 1 through 9 of this document.

      "Licensor" shall mean the copyright owner or entity authorized by
      the copyright owner that is granting the License.

      "Legal Entity" shall mean the union of the acting entity and all
      other entities that control, are controlled by, or are under common
      control with that entity. For the purposes of this definition,
      "control" means (i) the power, direct or indirect, to cause the
      direction or management of such entity, whether by contract or
      otherwise, or (ii) ownership of fifty percent (50%) or more of the
      outstanding shares, or (iii) beneficial ownership of such entity.

      "You" (or "Your") shall mean an individual or Legal Entity
      exercising permissions granted by this License.

      "Source" form shall mean the preferred form for making modifications,
      including but not limited to software source code, documentation
      source, and configuration files.

      "Object" form shall mean any form resulting from mechanical
      transformation or translation of a Source form, including but
      not limited to compiled object code, generated documentation,
      and conversions to other media types.

      "Work" shall mean the work of authorship, whether in Source or
      Object form, made available under the License, as indicated by a
      copyright notice that is included in or attached to the work
      (an example is provided in the Appendix below).

      "Derivative Works" shall mean any work, whether in Source or Object
      form, that is based on (or derived from) the Work and for which the
      editorial revisions, annotations, elaborations, or other modifications
      represent, as a whole, an original work of authorship. For the purposes
      of this License, Derivative Works shall not include works that remain
      separable from, or merely link (or bind by name) to the interfaces of,
      the Work and Derivative Works thereof.

      "Contribution" shall mean any work of authorship, including
      the original version of the Work and any modifications or additions
      to that Work or Derivative Works thereof, that is intentionally
      submitted to Licensor for inclusion in the Work by the copyright owner
      or by an individual or Legal Entity authorized to submit on behalf of
      the copyright owner. For the purposes of this definition, "submitted"
      means any form of electronic, verbal, or written communication sent
      to the Licensor or its representatives, including but not limited to
      communication on electronic mailing lists, source code control systems,
      and issue tracking systems that are managed by, or on behalf of, the
      Licensor for the purpose of discussing and improving the Work, but
      excluding communication that is conspicuously marked or otherwise
      designated in writing by the copyright owner as "Not a Contribution."

      "Contributor" shall mean Licensor and any individual or Legal Entity
      on behalf of whom a Contribution has been received by Licensor and
      subsequently incorporated within the Work.

   2. Grant of Copyright License. Subject to the terms and conditions of
      this License, each Contributor hereby grants to You a perpetual,
      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
      copyright license to reproduce, prepare Derivative Works of,
      publicly display, publicly perform, sublicense, and distribute the
      Work and such Derivative Works in Source or Object form.

   3. Grant of Patent License. Subject to the terms and conditions of
      this License, each Contributor hereby grants to You a perpetual,
      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
      (except as stated in this section) patent license to make, have made,
      use, offer to sell, sell, import, and otherwise transfer the Work,
      where such license applies only to those patent claims licensable
      by such Contributor that are necessarily infringed by their
      Contribution(s) alone or by combination of their Contribution(s)
      with the Work to which such Contribution(s) was submitted. If You
      institute patent litigation against any entity (including a
      cross-claim or counterclaim in a lawsuit) alleging that the Work
      or a Contribution incorporated within the Work constitutes direct
      or contributory patent infringement, then any patent licenses
      granted to You under this License for that Work shall terminate
      as of the date such litigation is filed.

   4. Redistribution. You may reproduce and distribute copies of the
      Work or Derivative Works thereof in any medium, with or without
      modifications, and in Source or Object form, provided that You
      meet the following conditions:

      (a) You must give any other recipients of the Work or
          Derivative Works a copy of this License; and

      (b) You must cause any modified files to carry prominent notices
          stating that You changed the files; and

      (c) You must retain, in the Source form of any Derivative Works
          that You distribute, all copyright, patent, trademark, and
          attribution notices from the Source form of the Work,
          excluding those notices that do not pertain to any part of
          the Derivative Works; and

      (d) If the Work includes a "NOTICE" text file as part of its
          distribution, then any Derivative Works that You distribute must
          include a readable copy of the attribution notices contained
          within such NOTICE file, excluding those notices that do not
          pertain to any part of the Derivative Works, in at least one
          of the following places: within a NOTICE text file distributed
          as part of the Derivative Works; within the Source form or
          documentation, if provided along with the Derivative Works; or,
          within a display generated by the Derivative Works, if and
          wherever such third-party notices normally appear. The contents
          of the NOTICE file are for informational purposes only and
          do not modify the License. You may add Your own attribution
          notices within Derivative Works that You distribute, alongside
          or as an addendum to the NOTICE text from the Work, provided
          that such additional attribution notices cannot be construed
          as modifying the License.

      You may add Your own copyright statement to Your modifications and
      may provide additional or different license terms and conditions
      for use, reproduction, or distribution of Your modifications, or
      for any such Derivative Works as a whole, provided Your use,
      reproduction, and distribution of the Work otherwise complies with
      the conditions stated in this License.

   5. Submission of Contributions. Unless You explicitly state otherwise,
      any Contribution intentionally submitted for inclusion in the Work
      by You to the Licensor shall be under the terms and conditions of
      this License, without any additional terms or conditions.
      Notwithstanding the above, nothing herein shall supersede or modify
      the terms of any separate license agreement you may have executed
      with Licensor regarding such Contributions.

   6. Trademarks. This License does not grant permission to use the trade
      names, trademarks, service marks, or product names of the Licensor,
      except as required for reasonable and customary use in describing the
      origin of the Work and reproducing the content of the NOTICE file.

   7. Disclaimer of Warranty. Unless required by applicable law or
      agreed to in writing, Licensor provides the Work (and each
      Contributor provides its Contributions) on an "AS IS" BASIS,
      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
      implied, including, without limitation, any warranties or conditions
      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
      PARTICULAR PURPOSE. You are solely responsible for determining the
      appropriateness of using or redistributing the Work and assume any
      risks associated with Your exercise of permissions under this License.

   8. Limitation of Liability. In no event and under no legal theory,
      whether in tort (including negligence), contract, or otherwise,
      unless required by applicable law (such as deliberate and grossly
      negligent acts) or agreed to in writing, shall any Contributor be
      liable to You for damages, including any direct, indirect, special,
      incidental, or consequential damages of any character arising as a
      result of this License or out of the use or inability to use the
      Work (including but not limited to damages for loss of goodwill,
      work stoppage, computer failure or malfunction, or any and all
      other commercial damages or losses), even if such Contributor
      has been advised of the possibility of such damages.

   9. Accepting Warranty or Additional Liability. While redistributing
      the Work or Derivative Works thereof, You may choose to offer,
      and charge a fee for, acceptance of support, warranty, indemnity,
      or other liability obligations and/or rights consistent with this
      License. However, in accepting such obligations, You may act only
      on Your own behalf and on Your sole responsibility, not on behalf
      of any other Contributor, and only if You agree to indemnify,
      defend, and hold each Contributor harmless for any liability
      incurred by, or claims asserted against, such Contributor by reason
      of your accepting any such warranty or additional liability.

   END OF TERMS AND CONDITIONS

   APPENDIX: How to apply the Apache License to your work.

      To apply the Apache License to your work, attach the following
      boilerplate notice, with the fields enclosed by brackets "[]"
      replaced with your own identifying information. (Don't include
      the brackets!)  The text should be enclosed in the appropriate
      comment syntax for the file format. We also recommend that a
      file or class name and description of purpose be included on the
      same "printed page" as the copyright notice for easier
      identification within third-party archives.

   Copyright [yyyy] [name of copyright owner]

   Licensed 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.


================================================
FILE: README.md
================================================
**English** | [中文](README_CN.md)
#  Jmqtt

![Jmqtt logo](jmqtt.png)

## Features
* Full support of mqtt3.1.1 protocol
* Support data persistence and clustering based on MySQL
* Support friendly secondary development, plug-in development: cluster / storage / device connection, publish subscribe authentication
* Support tcp, websocket, SSL, WSS

## Official documents
[Official documents](https://arrogant95.github.io/jmqtt-docs/)

## Quick start
1. Download [release](https://github.com/Cicizz/jmqtt/releases) (Version above 3. X) Or `clone` this project
2. Execute in the broker directory:`mvn -Ppackage-all -DskipTests clean install -U`
3. Configuration file for configuration response:`/jmqtt-broker/resources/conf`
4. Execute the start command:`java -jar jmqtt-broker-3.0.0.jar -h ${conf文件目录}` -H is followed by the configuration file directory, which needs to contain jmqtt.properties And log4j2. XML

## Online trial
Server address: 81.69.46.38
TCP port: 1883
Websocket port: 8883
SSL port: 1884
WSS port: 8884

## QQ technology exchange group
![jmqtt技术交流群](jmqtt_qq2.png)

## test report
jmqtt-doc/docs/TEST_REPORT.md





================================================
FILE: README_CN.md
================================================
[English](README.md) | **中文**
##  Jmqtt

![Jmqtt logo](jmqtt.png)

## 功能特性
* 完整支持mqtt3.1.1协议
* 支持基于mysql的数据持久化和集群
* 支持友好的二次开发,插件化开发:集群/存储/设备连接,发布订阅认证
* 支持tcp, websocket,ssl,wss

## 官方文档
[官方文档](https://arrogant95.github.io/jmqtt-docs/)

## 快速开始
1. 下载 [release](https://github.com/Cicizz/jmqtt/releases)(3.x以上版本) 或`clone`本项目
2. 在broker模块下执行:`mvn -Ppackage-all -DskipTests clean install -U`
3. 配置配置文件并初始化db的sql:`/jmqtt-broker/resources/conf`目录下
4. 执行启动命令:`java -jar jmqtt-broker-3.0.0.jar -h ${conf文件目录}` -h后是配置文件目录,里面需要包含jmqtt.properties和log4j2.xml等配置文件

## 技术交流群
欢迎小伙伴们给个star并使用。

![jmqtt技术交流群](jmqtt_qq2.png)

## 测试报告
https://www.yuque.com/tristan-ku8np/zze/xghq80




================================================
FILE: jmqtt-acceptance/README.md
================================================
这里是存放iot mq所有的功能测试,性能测试脚本及测试结果
## 功能测试计划(基于集群)
### 1.连接功能项
1. 连接
2. 连接 with cleasession = false(mqtt)
3. 连接 with will 消息(mqtt)
4. 使用ws连接
5. 连接使用ssl
6. 重新发起连接
### 2.发布消息
1. 发布qos0消息(mqtt)
2. 发布qos1消息(mqtt)
3. 发布qos2消息(mqtt)
4. 发布retain消息(mqtt)
### 3. 订阅
1. 订阅普通topic
2. 订阅多级topic
3. 订阅收到retain消息(mqtt)
4. 发布订阅拉通测试发送消息
### 4. 关闭连接


## 性能测试计划
## 1. 同时在线连接数压测
1. 连接数分级:100连接数/单机,500连接数/单机,1000连接数/单机,5000连接数/单机,1W连接数/单机
2. 同时请求连接:10/s,50/s,100/s,200/s,500/s  -- 单机
3. 集群测试(2+服务器):1W连接数/集群;5W连接数/集群;10W连接数/集群
4. 集群测试(2+服务器):1000/s,2000/s
5. 集群测试(2+服务器):同时在线10W设备,1000设备发送消息
## 2.发送消息TPS
消息字节大小:100byte[]
1. 单机:500/s,1000/s,5000/s,10000/s
2. 集群(2+台服务器):10000/s,15000/s,20000/s
## 3.订阅
1. 单设备订阅topic:10topic/单设备,50topic/单设备,100topic/单设备
2. 单机订阅树:10topic/单设备,50topic/单设备,100topic/单设备:分别连接1000设备,5000设备,10000设备
## 4.消费消息
1. 集群测试:同时在线10W设备,1000设备消费消息






================================================
FILE: jmqtt-acceptance/pom.xml
================================================
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>jmqtt</artifactId>
        <groupId>org.jmqtt</groupId>
        <version>3.0.0</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <maven.compiler.source>1.8</maven.compiler.source>
        <maven.compiler.target>1.8</maven.compiler.target>
    </properties>

    <artifactId>jmqtt-acceptance</artifactId>

    <dependencies>
        <dependency>
            <groupId>org.eclipse.paho</groupId>
            <artifactId>org.eclipse.paho.client.mqttv3</artifactId>
            <version>1.2.5</version>
        </dependency>

        <dependency>
            <groupId>commons-cli</groupId>
            <artifactId>commons-cli</artifactId>
            <version>${commons-cli.version}</version>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-assembly-plugin</artifactId>
                <configuration>
                    <descriptorRefs>
                        <descriptorRef>jar-with-dependencies</descriptorRef>
                    </descriptorRefs>
                </configuration>
                <executions>
                    <execution>
                        <id>make-assembly</id>
                        <phase>package</phase>
                        <goals>
                            <goal>single</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>

</project>


================================================
FILE: jmqtt-admin/pom.xml
================================================
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>jmqtt</artifactId>
        <groupId>org.jmqtt</groupId>
        <version>3.0.0</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>
    <version>3.0.0</version>
    <artifactId>jmqtt-admin</artifactId>

    <dependencies>
        <dependency>
            <groupId>org.jmqtt</groupId>
            <artifactId>jmqtt-broker</artifactId>
        </dependency>
        <dependency>
            <groupId>io.netty</groupId>
            <artifactId>netty-all</artifactId>
        </dependency>
    </dependencies>
</project>


================================================
FILE: jmqtt-broker/pom.xml
================================================
<?xml version="1.0" encoding="UTF-8"?>

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>jmqtt</artifactId>
        <groupId>org.jmqtt</groupId>
        <version>3.0.0</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>jmqtt-broker</artifactId>
    <version>3.0.0</version>

    <name>jmqtt-broker</name>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <maven.compiler.source>1.8</maven.compiler.source>
        <maven.compiler.target>1.8</maven.compiler.target>
        <akka.version>2.6.13</akka.version>
        <scala.binary.version>2.13</scala.binary.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.jmqtt</groupId>
            <artifactId>jmqtt-mqtt</artifactId>
        </dependency>

        <dependency>
            <groupId>org.assertj</groupId>
            <artifactId>assertj-core</artifactId>
        </dependency>

        <dependency>
            <groupId>org.junit.jupiter</groupId>
            <artifactId>junit-jupiter-api</artifactId>
            <scope>test</scope>
        </dependency>

    </dependencies>

    <!--打包插件 -->
    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <configuration>
                    <fork>true</fork>
                    <!-- 如果没有该配置,devtools不会生效 -->
                    <!-- 指定该Main Class为全局的唯一入口 -->
                    <mainClass>org.jmqtt.broker.BrokerStartup</mainClass>
                    <layout>ZIP</layout>
                </configuration>
                <executions>
                    <execution>
                        <goals>
                            <goal>repackage</goal><!--可以把依赖的包都打包到生成的Jar包中-->
                        </goals>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>

</project>


================================================
FILE: jmqtt-broker/src/main/java/org/jmqtt/broker/BrokerController.java
================================================
package org.jmqtt.broker;

import org.jmqtt.bus.BusController;
import org.jmqtt.mqtt.MQTTServer;
import org.jmqtt.support.config.BrokerConfig;
import org.jmqtt.support.config.NettyConfig;
import org.jmqtt.support.log.JmqttLogger;
import org.jmqtt.support.log.LogUtil;
import org.slf4j.Logger;

/**
 * 具体控制类:负责加载配置文件,初始化环境,启动服务等
 */
public class BrokerController {

    private static final Logger log = JmqttLogger.brokerLog;

    private BrokerConfig brokerConfig;
    private NettyConfig nettyConfig;

    /** bus */
    private BusController busController;

    /** mqtt gateway */
    private MQTTServer mqttServer;

    /** coap gateway todo */

    public BrokerController(BrokerConfig brokerConfig, NettyConfig nettyConfig){
        this.brokerConfig = brokerConfig;
        this.nettyConfig = nettyConfig;
        this.busController = new BusController(brokerConfig);
        this.mqttServer = new MQTTServer(busController,brokerConfig,nettyConfig);
    }

    public void start(){
        this.busController.start();
        this.mqttServer.start();
        LogUtil.info(log,"Jmqtt start. version:" + brokerConfig.getVersion());
    }


    public void shutdown(){
        this.mqttServer.shutdown();
        this.busController.shutdown();
    }
}


================================================
FILE: jmqtt-broker/src/main/java/org/jmqtt/broker/BrokerStartup.java
================================================
package org.jmqtt.broker;

import org.apache.commons.cli.*;
import org.apache.commons.lang3.StringUtils;
import org.apache.logging.log4j.Level;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.core.LoggerContext;
import org.apache.logging.log4j.core.config.Configuration;
import org.apache.logging.log4j.core.config.LoggerConfig;
import org.jmqtt.support.config.BrokerConfig;
import org.jmqtt.support.config.NettyConfig;
import org.jmqtt.support.helper.MixAll;

import java.io.*;
import java.util.Map;
import java.util.Objects;
import java.util.Properties;

/**
 * 技术问题,二次开发问题:请加 qq群:578185385
 */
public class BrokerStartup {

    public static void main(String[] args) {
        try {
            start(args);
        } catch (Exception e) {
            System.out.println("Jmqtt start failure,cause = " + e);
            e.printStackTrace();
            System.exit(-1);
        }
    }

    public static BrokerController start(String[] args) throws Exception {
        Options options = buildOptions();
        CommandLineParser parser = new DefaultParser();
        CommandLine commandLine = parser.parse(options,args);
        String jmqttHome = null;
        String logLevel = null;
        BrokerConfig brokerConfig = new BrokerConfig();
        NettyConfig nettyConfig = new NettyConfig();
        if(commandLine != null){
            jmqttHome = commandLine.getOptionValue("h");
            logLevel = commandLine.getOptionValue("l");
        }
        if(StringUtils.isEmpty(jmqttHome)){
            jmqttHome = brokerConfig.getJmqttHome();
        }
        if(StringUtils.isEmpty(jmqttHome)){
            throw new Exception("please set JMQTT_HOME.");
        }
        String jmqttConfigPath = jmqttHome + File.separator + "jmqtt.properties";
        initConfig(jmqttConfigPath,brokerConfig,nettyConfig);

        // 日志配置加载
        try {
            LoggerContext context = (org.apache.logging.log4j.core.LoggerContext) LogManager.getContext(false);
            File file = new File(jmqttHome + File.separator + "log4j2.xml");
            context.setConfigLocation(file.toURI());
            Configuration configuration = context.getConfiguration();
            Map<String, LoggerConfig> loggerConfigMap = configuration.getLoggers();
            Level newLevel = logLevel == null? null : Level.getLevel(logLevel);
            if (newLevel == null) {
                newLevel = Level.INFO;
            }
            for (LoggerConfig value : loggerConfigMap.values()) {
                value.setLevel(newLevel);
            }
            context.updateLoggers(configuration);
        } catch (Exception ex) {
            System.err.print("Log4j2 load error,ex:" + ex);
        }

        // 启动服务,线程等
        BrokerController brokerController = new BrokerController(brokerConfig,nettyConfig);
        brokerController.start();

        Runtime.getRuntime().addShutdownHook(new Thread(new Runnable() {
            @Override
            public void run() {
                brokerController.shutdown();
            }
        }));

        return brokerController;
    }

    private static Options buildOptions(){
        Options options = new Options();
        Option opt = new Option("h",true,"jmqttHome,eg: /wls/xxx");
        opt.setRequired(false);
        options.addOption(opt);

        opt = new Option("c",true,"jmqtt.properties path,eg: /wls/xxx/xxx.properties");
        opt.setRequired(false);
        options.addOption(opt);

        opt = new Option("l",true,"DEBUG");
        opt.setRequired(false);
        options.addOption(opt);

        return options;
    }

    /**
     * convert properties to java config class
     * @param jmqttConfigPath
     * @param brokerConfig
     * @param nettyConfig
     */
    private static void initConfig(String jmqttConfigPath, BrokerConfig brokerConfig, NettyConfig nettyConfig){
        Properties properties = new Properties();
        BufferedReader  bufferedReader = null;
        try {
            bufferedReader = new BufferedReader(new FileReader(jmqttConfigPath));
            properties.load(bufferedReader);
            MixAll.properties2POJO(properties,brokerConfig);
            MixAll.properties2POJO(properties,nettyConfig);
        } catch (FileNotFoundException e) {
            System.out.println("jmqtt.properties cannot find,cause + " + e + ",path:"+jmqttConfigPath);
        } catch (IOException e) {
            System.out.println("Handle jmqttConfig IO exception,cause = " + e);
        } finally {
            try {
                if(Objects.nonNull(bufferedReader)){
                    bufferedReader.close();
                }
            } catch (IOException e) {
                System.out.println("Handle jmqttConfig IO exception,cause = " + e);
            }
        }
    }

}


================================================
FILE: jmqtt-broker/src/main/resources/bin/jmqttshutdown
================================================
#!/bin/sh

pid=`ps ax | grep -i 'org.jmqtt.broker.BrokerStartup' |grep java | grep -v grep | awk '{print $1}'`
if [ -z "$pid" ] ; then
        echo "No jmqttBroker running."
        exit -1;
fi

echo "The jmqttBroker(${pid}) is running..."

kill ${pid}

echo "Send shutdown request to jmqttBroker(${pid}) OK"



================================================
FILE: jmqtt-broker/src/main/resources/bin/runbroker.sh
================================================
#!/bin/sh


JAVA_OPT="${JAVA_OPT} -server -Xms1280m -Xmx1280m -Xmn640m"
JAVA_OPT="${JAVA_OPT} -XX:+UseG1GC -XX:G1HeapRegionSize=16m -XX:G1ReservePercent=25 -XX:InitiatingHeapOccupancyPercent=30 -XX:SoftRefLRUPolicyMSPerMB=0 -XX:SurvivorRatio=8"
JAVA_OPT="${JAVA_OPT} -verbose:gc -Xloggc:/dev/shm/mq_gc_%p.log -XX:+PrintGCDetails -XX:+PrintGCDateStamps -XX:+PrintGCApplicationStoppedTime -XX:+PrintAdaptiveSizePolicy"
JAVA_OPT="${JAVA_OPT} -XX:+UseGCLogFileRotation -XX:NumberOfGCLogFiles=5 -XX:GCLogFileSize=30m"
JAVA_OPT="${JAVA_OPT} -XX:-OmitStackTraceInFastThrow"
JAVA_OPT="${JAVA_OPT} -XX:+AlwaysPreTouch"
JAVA_OPT="${JAVA_OPT} -XX:MaxDirectMemorySize=15g"
JAVA_OPT="${JAVA_OPT} -XX:-UseLargePages -XX:-UseBiasedLocking"

JAVA_OPT="${JAVA_OPT} -Dcom.sun.management.jmxremote.ssl=false"
JAVA_OPT="${JAVA_OPT} -Dcom.sun.management.jmxremote.authenticate=false"
JAVA_OPT="${JAVA_OPT} -Dcom.sun.management.jmxremote.port=1099"

#JAVA_OPT="${JAVA_OPT} -Xdebug -Xrunjdwp:transport=dt_socket,address=9555,server=y,suspend=n"
nohup java ${JAVA_OPT} -jar $@ >/dev/null 2>&1 &
echo 'jmqtt start.'


================================================
FILE: jmqtt-broker/src/main/resources/conf/client.cer
================================================
-----BEGIN CERTIFICATE-----
MIICWDCCAcGgAwIBAgIJANmxJ6Gxp3rbMA0GCSqGSIb3DQEBCwUAMEUxCzAJBgNV
BAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEwHwYDVQQKDBhJbnRlcm5ldCBX
aWRnaXRzIFB0eSBMdGQwHhcNMTkwNjMwMDk1ODE4WhcNMjkwNjI3MDk1ODE4WjBF
MQswCQYDVQQGEwJBVTETMBEGA1UECAwKU29tZS1TdGF0ZTEhMB8GA1UECgwYSW50
ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKB
gQCgz0+Bb1uY3TZhYx/bdBegTqLLsgmLlwHqdUuWbPEg9UdhLWWAeXpjXr+w1AjH
wQ0aQtDPc6bK6Bu8H11fw4RfrhEAx7TVnka/88gEUx3qivFtIt5fBDKN5dEE1PfM
kXqQV2szrJNRWsQbE8rbT26hNzc16Sdj0InLlJ9Y2pa0VQIDAQABo1AwTjAdBgNV
HQ4EFgQUD4R4hfIOYFFyR3gIi7PGOag7tcwwHwYDVR0jBBgwFoAUD4R4hfIOYFFy
R3gIi7PGOag7tcwwDAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQsFAAOBgQBHdaLA
RMg67YI0rEFc24MciGE+xBMSQEGegjIfo/p5frmU0kPBrdcv3XndblMPWDoGXnsE
j1KZHu5Mw6dg8kid4ytvqTgvVjcjwNZ8f6Uj2mhxPJa15K5FF0D3xgCes+WiA+wF
VDmoGEemCmGTIOjN4Nwb+V4/KKhOkoWhQBSLsg==
-----END CERTIFICATE-----


================================================
FILE: jmqtt-broker/src/main/resources/conf/jmqtt.properties
================================================
# 是否开启匿名访问
anonymousEnable=true

# max mqtt message size
maxMsgSize=524288


# 是否开启tcp服务
startTcp=true
tcpPort=1883
# 是否开启tcp ssl 服务
startSslTcp=true
sslTcpPort=1884

#集群模式:1.基于发布订阅,集群主动push消息给Jmqtt; 2.基于poll,jmqtt主动从集群拉消息
clusterMode = 2
# 采用2.poll方式时,一次从集群中最多拉的消息数目和间隔拉取的时间(ms)
maxPollEventNum = 10
pollWaitInterval = 10

# 是否开启websocket服务,ws协议
startWebsocket=true
websocketPort=8883

# 是否开启websocket服务,wss协议
startSslWebsocket=true
sslWebsocketPort=8884


# db config if jmqtt run with db
driver=com.mysql.cj.jdbc.Driver
url=jdbc:mysql://127.0.0.0:3306/jmqtt?characterEncoding=utf8&autoReconnect=true&failOverReadOnly=false&useSSL=false
username=root
password=CallmeZ2013


================================================
FILE: jmqtt-broker/src/main/resources/conf/jmqtt.sql
================================================
DROP INDEX `uk_client_id` ON `jmqtt_session`;
DROP INDEX `uk_client_id_topic` ON `jmqtt_subscription`;
DROP INDEX `idx_client_id` ON `jmqtt_message`;
DROP INDEX `idx_client_id_ack` ON `jmqtt_client_inbox`;
DROP INDEX `uk_topic` ON `jmqtt_retain_message`;


DROP TABLE `jmqtt_session`;
DROP TABLE `jmqtt_subscription`;
DROP TABLE `jmqtt_message`;
DROP TABLE `jmqtt_client_inbox`;
DROP TABLE `jmqtt_retain_message`;
DROP TABLE `jmqtt_cluster_event`;

CREATE TABLE `jmqtt_session` (
`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键',
`client_id` varchar(64) NOT NULL COMMENT '客户端id',
`online` varchar(12) NOT NULL COMMENT '状态:ONLINE,OFFLINE两种',
`transport_protocol` varchar(20) NOT NULL COMMENT '传输协议:MQTT,TCP,COAP等',
`client_ip` varchar(32) NOT NULL COMMENT '客户端ip',
`server_ip` varchar(32) NOT NULL COMMENT '连接的服务端ip',
`last_offline_time` timestamp NULL COMMENT '上一次离线时间',
`online_time` timestamp NOT NULL COMMENT '最近连接在线时间',
`properties` text NULL COMMENT '扩展信息',
PRIMARY KEY (`id`) ,
UNIQUE INDEX `uk_client_id` (`client_id` ASC)
)
COMMENT = '客户端会话状态';
CREATE TABLE `jmqtt_subscription` (
`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键',
`client_id` varchar(64) NOT NULL COMMENT '客户端id',
`topic` varchar(64) NOT NULL COMMENT '订阅的topic',
`subscribe_time` timestamp NOT NULL COMMENT '订阅时间',
`properties` text NULL COMMENT '订阅的额外属性',
PRIMARY KEY (`id`) ,
UNIQUE INDEX `uk_client_id_topic` (`client_id` ASC, `topic` ASC)
)
COMMENT = '客户端订阅关系';
CREATE TABLE `jmqtt_message` (
`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键id',
`source` varchar(32) NOT NULL COMMENT '消息来源:DEVICE/PLATFORM',
`content` mediumblob NOT NULL COMMENT '消息体内容:字节',
`topic` varchar(64) NULL COMMENT '发送的目标topic',
`from_client_id` varchar(64) NULL COMMENT '消息来源若是设备,从属设备id',
`stored_time` timestamp NOT NULL COMMENT '消息落库时间',
`properties` text NULL COMMENT '消息额外属性',
PRIMARY KEY (`id`) ,
INDEX `idx_client_id` (`source` ASC)
)
COMMENT = '入栈消息表';
CREATE TABLE `jmqtt_client_inbox` (
`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键id',
`client_id` varchar(64) NOT NULL COMMENT '客户端id',
`message_id` bigint(20) NOT NULL COMMENT '消息id',
`ack` tinyint(2) NOT NULL COMMENT '客户端是否收到消息:0未收到,1到达',
`stored_time` timestamp NOT NULL COMMENT '收件箱时间',
`ack_time` timestamp NULL,
PRIMARY KEY (`id`) ,
INDEX `idx_client_id_ack` (`client_id` ASC, `ack` ASC)
)
COMMENT = '设备消息收件箱表';
CREATE TABLE `jmqtt_retain_message` (
`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键',
`topic` varchar(64) NOT NULL COMMENT '所属topic',
`content` mediumblob NOT NULL COMMENT '消息体内容',
`from_client_id` varchar(64) NOT NULL COMMENT 'retain消息来源设备id',
`stored_time` timestamp NOT NULL COMMENT '存储事件',
`properties` text NOT NULL COMMENT '额外属性',
PRIMARY KEY (`id`) ,
UNIQUE INDEX `uk_topic` (`topic` ASC)
)
COMMENT = 'mqtt协议的retain 消息表';
CREATE TABLE `jmqtt_cluster_event` (
`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键:也是集群节点批量拉消息的offset',
`content` longtext NOT NULL COMMENT '消息体',
`gmt_create` timestamp(6) NOT NULL COMMENT '创建时间',
`node_ip` varchar(32) NOT NULL COMMENT 'jmqtt集群节点ip',
`event_code` varchar(64) NOT NULL COMMENT '事件码:参考代码',
PRIMARY KEY (`id`)
)
COMMENT = 'jmqtt 集群事件转发表:由发送端将消息发送到该表中,其他节点批量拉取该表中的事件进行处理';


================================================
FILE: jmqtt-broker/src/main/resources/conf/log4j2.xml
================================================
<?xml version="1.0" encoding="UTF-8"?>

<configuration>

    <properties>
        <!-- log file path -->
        <property name="user.dir" value="./" />
        <property name="level" value="INFO"/>
        <property name="pattern" value="%d [%X{traceId}/%X{loginUserID}/%X{remoteAddr}/%X{clientId} - %X{requestURIWithQueryString}] %-5p %c{2} - %m%n"/>
    </properties>

    <Appenders>
        <!-- console -->
        <Console name="console" target="SYSTEM_OUT" follow="true">
            <PatternLayout>
                <Pattern>${pattern}</Pattern>
            </PatternLayout>
        </Console>

        <!-- error -->
        <RollingFile name="error-appender" fileName="${user.dir}/jmqttlogs/error.log"
                     filePattern="${user.dir}/jmqttlogs/error.log.%d{yyyy-MM-dd}"
                     append="true">
            <ThresholdFilter level="ERROR" onMatch="ACCEPT" onMismatch="DENY"/>
            <PatternLayout
                    pattern="${pattern}"
                    charset="UTF-8"/>
            <TimeBasedTriggeringPolicy/>
            <DefaultRolloverStrategy>
                <Delete basePath="${user.dir}" maxDepth="1">
                    <IfFileName glob="*/common-error.log.*"/>
                    <IfLastModified age="10d"/>
                </Delete>
            </DefaultRolloverStrategy>
        </RollingFile>

        <!-- brokerLog -->
        <RollingFile name="brokerLog-appender" fileName="${user.dir}/jmqttlogs/brokerLog.log"
                     filePattern="${user.dir}/jmqttlogs/brokerLog.log.%d{yyyy-MM-dd}"
                     append="true">
            <PatternLayout
                    pattern="${pattern}"
                    charset="UTF-8"/>
            <TimeBasedTriggeringPolicy/>
            <DefaultRolloverStrategy>
                <Delete basePath="${user.dir}" maxDepth="1">
                    <IfFileName glob="*/brokerLog.log.*"/>
                    <IfLastModified age="10d"/>
                </Delete>
            </DefaultRolloverStrategy>
        </RollingFile>

        <!-- mqttLog -->
        <RollingFile name="mqttLog-appender" fileName="${user.dir}/jmqttlogs/mqttLog.log"
                     filePattern="${user.dir}/jmqttlogs/mqttLog.log.%d{yyyy-MM-dd}"
                     append="true">
            <PatternLayout
                    pattern="${pattern}"
                    charset="UTF-8"/>
            <TimeBasedTriggeringPolicy/>
            <DefaultRolloverStrategy>
                <Delete basePath="${user.dir}" maxDepth="1">
                    <IfFileName glob="*/mqttLog.log.*"/>
                    <IfLastModified age="10d"/>
                </Delete>
            </DefaultRolloverStrategy>
        </RollingFile>

        <!-- busLog -->
        <RollingFile name="busLog-appender" fileName="${user.dir}/jmqttlogs/busLog.log"
                     filePattern="${user.dir}/jmqttlogs/mqttLog.log.%d{yyyy-MM-dd}"
                     append="true">
            <PatternLayout
                    pattern="${pattern}"
                    charset="UTF-8"/>
            <TimeBasedTriggeringPolicy/>
            <DefaultRolloverStrategy>
                <Delete basePath="${user.dir}" maxDepth="1">
                    <IfFileName glob="*/busLog.log.*"/>
                    <IfLastModified age="10d"/>
                </Delete>
            </DefaultRolloverStrategy>
        </RollingFile>

        <!-- clientTraceLog -->
        <RollingFile name="clientTraceLog-appender" fileName="${user.dir}/jmqttlogs/clientTraceLog.log"
                     filePattern="${user.dir}/jmqttlogs/clientTraceLog.log.%d{yyyy-MM-dd}"
                     append="true">
            <PatternLayout
                    pattern="${pattern}"
                    charset="UTF-8"/>
            <TimeBasedTriggeringPolicy/>
            <DefaultRolloverStrategy>
                <Delete basePath="${user.dir}" maxDepth="1">
                    <IfFileName glob="*/clientTraceLog.log.*"/>
                    <IfLastModified age="10d"/>
                </Delete>
            </DefaultRolloverStrategy>
        </RollingFile>

        <!-- clientTraceLog -->
        <RollingFile name="messageTraceLog-appender" fileName="${user.dir}/jmqttlogs/messageTraceLog.log"
                     filePattern="${user.dir}/jmqttlogs/messageTraceLog.log.%d{yyyy-MM-dd}"
                     append="true">
            <PatternLayout
                    pattern="${pattern}"
                    charset="UTF-8"/>
            <TimeBasedTriggeringPolicy/>
            <DefaultRolloverStrategy>
                <Delete basePath="${user.dir}" maxDepth="1">
                    <IfFileName glob="*/messageTraceLog.log.*"/>
                    <IfLastModified age="10d"/>
                </Delete>
            </DefaultRolloverStrategy>
        </RollingFile>

        <!-- eventLog -->
        <RollingFile name="eventLog-appender" fileName="${user.dir}/jmqttlogs/eventLog.log"
                     filePattern="${user.dir}/jmqttlogs/eventLog.log.%d{yyyy-MM-dd}"
                     append="true">
            <PatternLayout
                    pattern="${pattern}"
                    charset="UTF-8"/>
            <TimeBasedTriggeringPolicy/>
            <DefaultRolloverStrategy>
                <Delete basePath="${user.dir}" maxDepth="1">
                    <IfFileName glob="*/eventLog.log.*"/>
                    <IfLastModified age="10d"/>
                </Delete>
            </DefaultRolloverStrategy>
        </RollingFile>

        <!-- remotingLog -->
        <RollingFile name="remotingLog-appender" fileName="${user.dir}/jmqttlogs/remotingLog.log"
                     filePattern="${user.dir}/jmqttlogs/remotingLog.log.%d{yyyy-MM-dd}"
                     append="true">
            <PatternLayout
                    pattern="${pattern}"
                    charset="UTF-8"/>
            <TimeBasedTriggeringPolicy/>
            <DefaultRolloverStrategy>
                <Delete basePath="${user.dir}" maxDepth="1">
                    <IfFileName glob="*/remotingLog.log.*"/>
                    <IfLastModified age="10d"/>
                </Delete>
            </DefaultRolloverStrategy>
        </RollingFile>

        <!-- storeLog -->
        <RollingFile name="storeLog-appender" fileName="${user.dir}/jmqttlogs/storeLog.log"
                     filePattern="${user.dir}/jmqttlogs/storeLog.log.%d{yyyy-MM-dd}"
                     append="true">
            <PatternLayout
                    pattern="${pattern}"
                    charset="UTF-8"/>
            <TimeBasedTriggeringPolicy/>
            <DefaultRolloverStrategy>
                <Delete basePath="${user.dir}" maxDepth="1">
                    <IfFileName glob="*/storeLog.log.*"/>
                    <IfLastModified age="10d"/>
                </Delete>
            </DefaultRolloverStrategy>
        </RollingFile>

        <!-- otherLog -->
        <RollingFile name="otherLog-appender" fileName="${user.dir}/jmqttlogs/otherLog.log"
                     filePattern="${user.dir}/jmqttlogs/otherLog.log.%d{yyyy-MM-dd}"
                     append="true">
            <PatternLayout
                    pattern="${pattern}"
                    charset="UTF-8"/>
            <TimeBasedTriggeringPolicy/>
            <DefaultRolloverStrategy>
                <Delete basePath="${user.dir}" maxDepth="1">
                    <IfFileName glob="*/otherLog.log.*"/>
                    <IfLastModified age="10d"/>
                </Delete>
            </DefaultRolloverStrategy>
        </RollingFile>
    </Appenders>

    <Loggers>
        <logger name="brokerLog" level="${level}" additivity="false">
            <appender-ref ref="brokerLog-appender"/>
            <appender-ref ref="error-appender"/>
            <appender-ref ref="console"/>
        </logger>
        <logger name="mqttLog" level="${level}" additivity="false">
            <appender-ref ref="mqttLog-appender"/>
            <appender-ref ref="error-appender"/>
            <appender-ref ref="console"/>
        </logger>
        <logger name="busLog" level="${level}" additivity="false">
            <appender-ref ref="busLog-appender"/>
            <appender-ref ref="error-appender"/>
            <appender-ref ref="console"/>
        </logger>
        <logger name="clientTraceLog" level="${level}" additivity="false">
            <appender-ref ref="clientTraceLog-appender"/>
            <appender-ref ref="error-appender"/>
            <appender-ref ref="console"/>
        </logger>
        <logger name="messageTraceLog" level="${level}" additivity="false">
            <appender-ref ref="messageTraceLog-appender"/>
            <appender-ref ref="error-appender"/>
            <appender-ref ref="console"/>
        </logger>
        <logger name="eventLog" level="${level}" additivity="false">
            <appender-ref ref="eventLog-appender"/>
            <appender-ref ref="error-appender"/>
            <appender-ref ref="console"/>
        </logger>
        <logger name="remotingLog" level="${level}" additivity="false">
            <appender-ref ref="remotingLog-appender"/>
            <appender-ref ref="error-appender"/>
            <appender-ref ref="console"/>
        </logger>
        <logger name="storeLog" level="${level}" additivity="false">
            <appender-ref ref="storeLog-appender"/>
            <appender-ref ref="error-appender"/>
            <appender-ref ref="console"/>
        </logger>
        <logger name="otherLog" level="${level}" additivity="false">
            <appender-ref ref="otherLog-appender"/>
            <appender-ref ref="error-appender"/>
            <appender-ref ref="console"/>
        </logger>

        <root>
            <level value="${level}"/>
            <appender-ref ref="error-appender"/>
        </root>
    </Loggers>
</configuration>


================================================
FILE: jmqtt-broker/src/test/java/org/jmqtt/broker/test/Runtime.java
================================================
package org.jmqtt.broker.test;

import com.sun.management.OperatingSystemMXBean;
import org.junit.jupiter.api.Test;

import java.lang.management.ManagementFactory;

public class Runtime {

    @Test
    public void subscribe() {
        // 虚拟机级内存情况查询
        long vmFree = 0;
        long vmUse = 0;
        long vmTotal = 0;
        long vmMax = 0;
        int byteToMb = 1024 * 1024;
        java.lang.Runtime rt = java.lang.Runtime.getRuntime();
        vmTotal = rt.totalMemory() / byteToMb;
        vmFree = rt.freeMemory() / byteToMb;
        vmMax = rt.maxMemory() / byteToMb;
        vmUse = vmTotal - vmFree;
        System.out.println("JVM内存已用的空间为:" + vmUse + " MB");
        System.out.println("JVM内存的空闲空间为:" + vmFree + " MB");
        System.out.println("JVM总内存空间为:" + vmTotal + " MB");
        System.out.println("JVM总内存空间为:" + vmMax + " MB");

        System.out.println("======================================");
        // 操作系统级内存情况查询
        OperatingSystemMXBean osmxb = (OperatingSystemMXBean) ManagementFactory.getOperatingSystemMXBean();
        String os = System.getProperty("os.name");
        long physicalFree = osmxb.getFreePhysicalMemorySize() / byteToMb;
        long physicalTotal = osmxb.getTotalPhysicalMemorySize() / byteToMb;
        long physicalUse = physicalTotal - physicalFree;
        System.out.println("操作系统的版本:" + os);
        System.out.println("操作系统物理内存已用的空间为:" + physicalFree + " MB");
        System.out.println("操作系统物理内存的空闲空间为:" + physicalUse + " MB");
        System.out.println("操作系统总物理内存:" + physicalTotal + " MB");
        // 获得线程总数
        ThreadGroup parentThread;
        int totalThread = 0;
        for (parentThread = Thread.currentThread().getThreadGroup(); parentThread
                .getParent() != null; parentThread = parentThread.getParent()) {
            totalThread = parentThread.activeCount();
        }
        System.out.println("获得线程总数:" + totalThread);

        ThreadGroup currentGroup =
                Thread.currentThread().getThreadGroup();
        int noThreads = currentGroup.activeCount();
        Thread[] lstThreads = new Thread[noThreads];
        currentGroup.enumerate(lstThreads);
        for (int i = 0; i < noThreads; i++)
            System.out.println("线程号:" + i + " = " + lstThreads[i].getName());
    }
}


================================================
FILE: jmqtt-bus/README.md
================================================
iot 消息bus


================================================
FILE: jmqtt-bus/pom.xml
================================================
<?xml version="1.0" encoding="UTF-8"?>

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>jmqtt</artifactId>
        <groupId>org.jmqtt</groupId>
        <version>3.0.0</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>jmqtt-bus</artifactId>

    <name>jmqtt-bus</name>

    <dependencies>

        <dependency>
            <groupId>org.assertj</groupId>
            <artifactId>assertj-core</artifactId>
        </dependency>

        <dependency>
            <groupId>com.google.guava</groupId>
            <artifactId>guava</artifactId>
        </dependency>

        <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis</artifactId>
        </dependency>

        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid</artifactId>
        </dependency>

        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
        </dependency>
        <dependency>
            <groupId>org.apache.logging.log4j</groupId>
            <artifactId>log4j-api</artifactId>
        </dependency>
        <dependency>
            <groupId>org.apache.logging.log4j</groupId>
            <artifactId>log4j-core</artifactId>
        </dependency>

        <dependency>
            <groupId>org.jmqtt</groupId>
            <artifactId>jmqtt-support</artifactId>
        </dependency>
        <dependency>
            <groupId>org.junit.jupiter</groupId>
            <artifactId>junit-jupiter-api</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>
</project>


================================================
FILE: jmqtt-bus/src/main/java/org/jmqtt/bus/Authenticator.java
================================================
package org.jmqtt.bus;


public interface Authenticator {


    boolean login(String clientId,String userName,byte[] password);

    boolean onBlackList(String clientId,String remoteIpAddress);

    boolean clientIdVerify(String clientId);

    boolean subscribeVerify(String clientId,String topic);
}


================================================
FILE: jmqtt-bus/src/main/java/org/jmqtt/bus/BusController.java
================================================
package org.jmqtt.bus;

import org.jmqtt.bus.impl.*;
import org.jmqtt.bus.store.DBUtils;
import org.jmqtt.bus.subscription.CTrieSubscriptionMatcher;
import org.jmqtt.bus.subscription.SubscriptionMatcher;
import org.jmqtt.support.config.BrokerConfig;

public class BusController {

    private BrokerConfig brokerConfig;
    private Authenticator authenticator;
    private DeviceSessionManager deviceSessionManager;
    private DeviceMessageManager deviceMessageManager;
    private DeviceSubscriptionManager deviceSubscriptionManager;
    private SubscriptionMatcher subscriptionMatcher;
    private ClusterEventManager clusterEventManager;

    public BusController(BrokerConfig brokerConfig){
        this.brokerConfig = brokerConfig;
        this.authenticator = new DefaultAuthenticator();
        this.deviceMessageManager = new DeviceMessageManagerImpl();
        this.deviceSessionManager = new DeviceSessionManagerImpl();
        //this.subscriptionMatcher = new DefaultSubscriptionTreeMatcher();
        this.subscriptionMatcher = new CTrieSubscriptionMatcher();
        this.deviceSubscriptionManager = new DeviceSubscriptionManagerImpl(subscriptionMatcher);
        this.clusterEventManager = new ClusterEventManagerImpl(subscriptionMatcher);
    }


    public void start(){
        DBUtils.getInstance().start(brokerConfig);
        this.clusterEventManager.start();
    }

    public void shutdown(){
        this.clusterEventManager.shutdown();
        DBUtils.getInstance().shutdown();
    }

    public Authenticator getAuthenticator() {
        return authenticator;
    }

    public DeviceSessionManager getDeviceSessionManager() {
        return deviceSessionManager;
    }

    public DeviceMessageManager getDeviceMessageManager() {
        return deviceMessageManager;
    }

    public DeviceSubscriptionManager getDeviceSubscriptionManager() {
        return deviceSubscriptionManager;
    }

    public ClusterEventManager getClusterEventManager() {
        return clusterEventManager;
    }

    public SubscriptionMatcher getSubscriptionMatcher() {
        return subscriptionMatcher;
    }
}


================================================
FILE: jmqtt-bus/src/main/java/org/jmqtt/bus/ClusterEventManager.java
================================================
package org.jmqtt.bus;

import org.jmqtt.bus.event.GatewayListener;
import org.jmqtt.bus.model.ClusterEvent;

/**
 * cluster event send and listen
 */
public interface ClusterEventManager {

    void sendEvent(ClusterEvent clusterEvent);

    void registerEventListener(GatewayListener listener);

    void start();

    void shutdown();
}


================================================
FILE: jmqtt-bus/src/main/java/org/jmqtt/bus/DeviceMessageManager.java
================================================

package org.jmqtt.bus;

import org.jmqtt.bus.enums.MessageAckEnum;
import org.jmqtt.bus.model.DeviceMessage;

import java.util.List;

public interface DeviceMessageManager {

    void clearUnAckMessage(String clientId);

    /**
     * send message to bus
     * @param deviceMessage
     */
    void dispatcher(DeviceMessage deviceMessage);

    Long storeMessage(DeviceMessage deviceMessage);

    List<DeviceMessage> queryUnAckMessages(String clientId,int limit);

    List<DeviceMessage> queryByIds(List<Long> ids);

    Long addClientInBoxMsg(String clientId,Long messageId, MessageAckEnum ackEnum);

    boolean ackMessage(String clientId,Long messageId);
}


================================================
FILE: jmqtt-bus/src/main/java/org/jmqtt/bus/DeviceSessionManager.java
================================================

package org.jmqtt.bus;

import org.jmqtt.bus.model.DeviceSession;

public interface DeviceSessionManager {



    DeviceSession getSession(String clientId);

    void storeSession(DeviceSession deviceSession);


    void offline(String clientId);
}


================================================
FILE: jmqtt-bus/src/main/java/org/jmqtt/bus/DeviceSubscriptionManager.java
================================================
package org.jmqtt.bus;

import org.jmqtt.bus.model.DeviceSubscription;

import java.util.Set;

public interface DeviceSubscriptionManager {

    /**
     * 1. Add subscription to the sub tree
     * 2. Persistence subscription
     * @param deviceSubscription
     * @return true:sub success  false: sub fail
     */
    boolean subscribe(DeviceSubscription deviceSubscription);

    /**
     * 1. Remove subscription from the sub tree
     * 2. Delete subscription from db
     */
    boolean unSubscribe(String clientId,String topic);

    boolean isMatch(String pubTopic,String subTopic);

    boolean onlySubscribe2Tree(DeviceSubscription deviceSubscription);

    boolean onlyUnUnSubscribeFromTree(String clientId,String topic);

    Set<DeviceSubscription> getAllSubscription(String clientId);

    void deleteAllSubscription(String clientId);
}


================================================
FILE: jmqtt-bus/src/main/java/org/jmqtt/bus/enums/ClusterEventCodeEnum.java
================================================
package org.jmqtt.bus.enums;

/**
 * cluster event code
 */
public enum ClusterEventCodeEnum {

    MQTT_CLEAR_SESSION("MQTT_CLEAR_SESSION"),

    DISPATCHER_CLIENT_MESSAGE("DISPATCHER_CLIENT_MESSAGE"),
    ;

    private String    code;

    ClusterEventCodeEnum(String code) {
        this.code = code;
    }

    public String getCode() {
        return code;
    }
}


================================================
FILE: jmqtt-bus/src/main/java/org/jmqtt/bus/enums/DeviceOnlineStateEnum.java
================================================

package org.jmqtt.bus.enums;

public enum DeviceOnlineStateEnum {

    ONLINE("ONLINE"),

    OFFLINE("OFFLINE"),

    ;

    private String code;

    DeviceOnlineStateEnum(String code) {
        this.code = code;
    }

    public String getCode() {
        return code;
    }
}


================================================
FILE: jmqtt-bus/src/main/java/org/jmqtt/bus/enums/MessageAckEnum.java
================================================

package org.jmqtt.bus.enums;

public enum MessageAckEnum {

    UN_ACK(0),

    ACK(1),

    ;

    private int code;

    MessageAckEnum(int code) {
        this.code = code;
    }

    public int getCode() {
        return code;
    }

    public static MessageAckEnum getByCode(int code) {
        for (MessageAckEnum value : MessageAckEnum.values()) {
            if (value.code == code) {
                return value;
            }
        }
        return null;
    }
}


================================================
FILE: jmqtt-bus/src/main/java/org/jmqtt/bus/enums/MessageFlowEnum.java
================================================
package org.jmqtt.bus.enums;


public enum MessageFlowEnum {

    INBOUND("INBOUND"),

    OUTBOUND("OUTBOUND"),

            ;

    private String code;

    MessageFlowEnum(String code) {
        this.code = code;
    }

    public String getCode() {
        return code;
    }
}


================================================
FILE: jmqtt-bus/src/main/java/org/jmqtt/bus/enums/MessageSourceEnum.java
================================================
package org.jmqtt.bus.enums;


public enum MessageSourceEnum {

    DEVICE("DEVICE"),

    PLATFORM("PLATFORM"),

            ;

    private String code;

    MessageSourceEnum(String code) {
        this.code = code;
    }

    public String getCode() {
        return code;
    }
}


================================================
FILE: jmqtt-bus/src/main/java/org/jmqtt/bus/enums/TransportProtocolEnum.java
================================================

package org.jmqtt.bus.enums;


public enum TransportProtocolEnum {

    MQTT("MQTT"),

    TCP("TCP"),

    COAP("COAP"),
    ;

    private String code;

    TransportProtocolEnum(String code) {
        this.code = code;
    }

    public String getCode() {
        return code;
    }

}


================================================
FILE: jmqtt-bus/src/main/java/org/jmqtt/bus/event/EventCenter.java
================================================
package org.jmqtt.bus.event;

import com.google.common.eventbus.EventBus;
import org.jmqtt.bus.model.ClusterEvent;

public class EventCenter {

    private final EventBus eventBus;

    public EventCenter(){
        this.eventBus = new EventBus();
    }

    /**
     * register gateway listener
     * @param listener
     */
    public void register(GatewayListener listener){
        this.eventBus.register(listener);
    }

    /**
     * send event to bus,and gateway will consume this event
     * @param clusterEvent
     */
    public void sendEvent(ClusterEvent clusterEvent){
        this.eventBus.post(clusterEvent);
    }
}


================================================
FILE: jmqtt-bus/src/main/java/org/jmqtt/bus/event/GatewayListener.java
================================================

package org.jmqtt.bus.event;

import org.jmqtt.bus.model.ClusterEvent;

/**
 * gateway listener
 * iot gateway 实现该接口,接收bus的事件进行客户端触达和处理
 */
public interface GatewayListener {

    void consume(ClusterEvent clusterEvent);
}


================================================
FILE: jmqtt-bus/src/main/java/org/jmqtt/bus/impl/ClusterEventManagerImpl.java
================================================
package org.jmqtt.bus.impl;

import com.alibaba.fastjson.JSONObject;
import org.apache.ibatis.session.SqlSession;
import org.jmqtt.bus.ClusterEventManager;
import org.jmqtt.bus.enums.ClusterEventCodeEnum;
import org.jmqtt.bus.event.EventCenter;
import org.jmqtt.bus.event.GatewayListener;
import org.jmqtt.bus.model.ClusterEvent;
import org.jmqtt.bus.model.DeviceMessage;
import org.jmqtt.bus.store.DBCallback;
import org.jmqtt.bus.store.DBUtils;
import org.jmqtt.bus.store.daoobject.EventDO;
import org.jmqtt.bus.subscription.SubscriptionMatcher;
import org.jmqtt.bus.subscription.model.Subscription;
import org.jmqtt.support.helper.MixAll;
import org.jmqtt.support.log.JmqttLogger;
import org.jmqtt.support.log.LogUtil;
import org.slf4j.Logger;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicLong;


/**
 * send current node's event to other nodes 集群事件处理,两种方式,根据实际情况,选一个实现即可:
 * 1. 发布订阅: a. 发送消息 A -> mq系统转发 b. 第二阶段:消息订阅分发给本节点的设备 jmqtt B,C,D —> jmqtt B的连接设备
 * 2. jmqtt服务主动拉取: a. A —> 消息存储服务: b. 第二阶段:主动拉取 jmqtt B,C,D broker 定时批量拉取 —> 从消息存储服务拉取 —> 拉取后推送给 B,C,D上连接的设备
 */
public class ClusterEventManagerImpl implements ClusterEventManager {

    private static final Logger        log        = JmqttLogger.busLog;
    private static final AtomicLong    offset     = new AtomicLong();
    private          AtomicBoolean pollStoped = new AtomicBoolean(false);
    private int                    maxPollNum = 100;
    private int                    pollWaitInterval = 10; // ms
    private final EventCenter eventCenter;
    private SubscriptionMatcher subscriptionMatcher;

    public ClusterEventManagerImpl(SubscriptionMatcher subscriptionMatcher){
        this.eventCenter = new EventCenter();
        this.subscriptionMatcher = subscriptionMatcher;
    }

    @Override
    public void start() {
        Long maxId = DBUtils.operate(new DBCallback() {
            @Override
            public Long operate(SqlSession sqlSession) {
                return DBUtils.getMapper(sqlSession,DBUtils.eventMapperClass).getMaxOffset();
            }
        });
        if (maxId == null) {
            offset.set(0);
        } else {
            offset.set(maxId);
        }
        new Thread(() -> {
            while (!pollStoped.get()) {
                try {
                    List<ClusterEvent> eventList = pollEvent(maxPollNum);
                    if (!MixAll.isEmpty(eventList)) {
                        for (ClusterEvent event : eventList) {
                            // Send event to iot gateway
                            consumeEvent(event);
                        }
                    }
                    if (MixAll.isEmpty(eventList) || eventList.size() < 5) {
                        Thread.sleep(pollWaitInterval);
                    }
                } catch (Exception e) {
                    LogUtil.warn(log, "Poll event from cluster error.", e);
                }
            }
        }).start();

        LogUtil.info(log,"Cluster event server start.");
    }

    @Override
    public void shutdown() {
        this.pollStoped.compareAndSet(false,true);
    }

    /**
     * 集群消息往这里投递
     */
    private void consumeEvent(ClusterEvent event){

        // 若是消息,进行订阅树的匹配
        if (event.getClusterEventCode() == ClusterEventCodeEnum.DISPATCHER_CLIENT_MESSAGE){
            DeviceMessage deviceMessage = JSONObject.parseObject(event.getContent(),DeviceMessage.class);
            if (deviceMessage == null) {
                LogUtil.error(log,"[BUS EVENT] event content is empty.");
                return;
            }
            Set<Subscription> subscriptionSet = this.subscriptionMatcher.match(deviceMessage.getTopic());
            deviceMessage.setContent(null);
            if (subscriptionSet != null) {
                subscriptionSet.forEach(item -> {
                    ClusterEvent stayEvent = event.clone();
                    stayEvent.setSubscription(item);

                    eventCenter.sendEvent(stayEvent);
                });
            }
            return;
        }
        eventCenter.sendEvent(event);
    }


    private List<ClusterEvent> pollEvent(int maxPollNum) {
        // offset: min -> max
        long currentOffset = offset.get();
        List<EventDO> eventDOList =  DBUtils.operate(new DBCallback() {
            @Override
            public List<EventDO> operate(SqlSession sqlSession) {
                return DBUtils.getMapper(sqlSession,DBUtils.eventMapperClass).consumeEvent(currentOffset,maxPollNum);
            }
        });
        if (eventDOList == null || eventDOList.size() == 0) {
            return Collections.emptyList();
        }
        List<ClusterEvent> events = new ArrayList<>();
        for (EventDO eventDO : eventDOList) {
            ClusterEvent event = new ClusterEvent();
            event.setNodeIp(eventDO.getNodeIp());
            event.setGmtCreate(eventDO.getGmtCreate());
            event.setContent(eventDO.getContent());
            event.setClusterEventCode(ClusterEventCodeEnum.valueOf(eventDO.getEventCode()));
            events.add(event);
        }

        // reset offset
        EventDO eventDO = eventDOList.get(eventDOList.size()-1);
        if (!offset.compareAndSet(currentOffset,eventDO.getId())) {
            LogUtil.warn(log,"[RDBClusterEventHandler] pollEvent offset is wrong,expectOffset:{},currentOffset:{},maxOffset:{}",
                    offset.get(),currentOffset,eventDO.getId());
            offset.set(eventDO.getId());
        }
        return events;
    }

    @Override
    public void sendEvent(ClusterEvent clusterEvent) {
        EventDO eventDO = convert(clusterEvent);
        DBUtils.operate(new DBCallback() {
            @Override
            public Object operate(SqlSession sqlSession) {
                return DBUtils.getMapper(sqlSession, DBUtils.eventMapperClass).sendEvent(eventDO);
            }
        });
    }


    public void registerEventListener(GatewayListener listener){
        this.eventCenter.register(listener);
    }


    private EventDO convert(ClusterEvent clusterEvent) {
        EventDO eventDO = new EventDO();
        eventDO.setContent(clusterEvent.getContent());
        eventDO.setNodeIp(clusterEvent.getNodeIp());
        eventDO.setEventCode(clusterEvent.getClusterEventCode().getCode());
        eventDO.setGmtCreate(clusterEvent.getGmtCreate());
        return eventDO;
    }
}


================================================
FILE: jmqtt-bus/src/main/java/org/jmqtt/bus/impl/DefaultAuthenticator.java
================================================
package org.jmqtt.bus.impl;

import org.jmqtt.bus.Authenticator;


public class DefaultAuthenticator implements Authenticator {

    @Override
    public boolean login(String clientId, String userName, byte[] password) {
        return true;
    }

    @Override
    public boolean onBlackList(String clientId, String remoteIpAddress) {
        return false;
    }

    @Override
    public boolean clientIdVerify(String clientId) {
        return true;
    }

    @Override
    public boolean subscribeVerify(String clientId, String topic) {
        return true;
    }
}


================================================
FILE: jmqtt-bus/src/main/java/org/jmqtt/bus/impl/DeviceMessageManagerImpl.java
================================================
package org.jmqtt.bus.impl;

import com.alibaba.fastjson.JSONObject;
import org.apache.ibatis.session.SqlSession;
import org.jmqtt.bus.DeviceMessageManager;
import org.jmqtt.bus.enums.ClusterEventCodeEnum;
import org.jmqtt.bus.enums.MessageAckEnum;
import org.jmqtt.bus.enums.MessageSourceEnum;
import org.jmqtt.bus.model.DeviceInboxMessage;
import org.jmqtt.bus.model.DeviceMessage;
import org.jmqtt.bus.store.DBCallback;
import org.jmqtt.bus.store.DBUtils;
import org.jmqtt.bus.store.daoobject.DeviceInboxMessageDO;
import org.jmqtt.bus.store.daoobject.EventDO;
import org.jmqtt.bus.store.daoobject.MessageDO;
import org.jmqtt.support.log.JmqttLogger;
import org.jmqtt.support.log.LogUtil;
import org.jmqtt.support.remoting.RemotingHelper;
import org.slf4j.Logger;

import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Map;

public class DeviceMessageManagerImpl implements DeviceMessageManager {

    private static final Logger log = JmqttLogger.busLog;

    @Override
    public void clearUnAckMessage(String clientId) {
        DBUtils.operate(new DBCallback() {
            @Override
            public Integer operate(SqlSession sqlSession) {
                return DBUtils.getMapper(sqlSession,DBUtils.clientInboxMessageMapperClass).truncateUnAck(clientId);
            }
        });
    }

    @Override
    public void dispatcher(DeviceMessage deviceMessage) {

        // store message and send event
        SqlSession sqlSession = null;
        try {
            sqlSession = DBUtils.getSqlSessionWithTrans();
            MessageDO messageDO = convert(deviceMessage);
            DBUtils.getMapper(sqlSession,DBUtils.messageMapperClass).storeMessage(messageDO);
            Long id = messageDO.getId();
            if (id == null || id <= 0) {
                LogUtil.error(log,"[BUS] store message failure.");
                return;
            }
            deviceMessage.setId(id);
            EventDO eventDO = convert2Event(deviceMessage);
            DBUtils.getMapper(sqlSession,DBUtils.eventMapperClass).sendEvent(eventDO);
            Long eventId = eventDO.getId();
            if (eventId == null || eventId <= 0) {
                LogUtil.error(log,"[BUS] send event message failure.");
                sqlSession.rollback();
                return;
            }
            sqlSession.commit();
        } catch (Exception ex) {
            LogUtil.error(log,"[BUS] dispatcher message store caught exception,{}",ex);
            if (sqlSession != null) {
                sqlSession.rollback();
            }
        } finally {
            if (sqlSession != null) {
                sqlSession.close();
            }
        }
    }

    @Override
    public Long storeMessage(DeviceMessage deviceMessage) {
        MessageDO messageDO = convert(deviceMessage);
        DBUtils.operate(new DBCallback() {
            @Override
            public Long operate(SqlSession sqlSession) {
                 return DBUtils.getMapper(sqlSession,DBUtils.messageMapperClass).storeMessage(messageDO);
            }
        });
        return messageDO.getId();
    }

    @Override
    public List<DeviceMessage> queryUnAckMessages(String clientId, int pageSize) {
        List<DeviceInboxMessageDO> inboxMessageDOS =  DBUtils.operate(new DBCallback() {
            @Override
            public List<DeviceInboxMessageDO> operate(SqlSession sqlSession) {
                return DBUtils.getMapper(sqlSession, DBUtils.clientInboxMessageMapperClass).getUnAckMessages(clientId,pageSize);
            }
        });

        if (inboxMessageDOS == null) {
            return null;
        }
        List<Long> ids = new ArrayList<>();
        inboxMessageDOS.forEach(itemDO -> {
            ids.add(itemDO.getMessageId());
        });
        return queryByIds(ids);
    }

    @Override
    public List<DeviceMessage> queryByIds(List<Long> ids) {
        List<MessageDO> messages =  DBUtils.operate(new DBCallback() {
            @Override
            public List<MessageDO> operate(SqlSession sqlSession) {
                return DBUtils.getMapper(sqlSession, DBUtils.messageMapperClass).queryMessageByIds(ids);
            }
        });

        List<DeviceMessage> deviceMessageList = new ArrayList<>(messages.size());
        messages.forEach(item -> {
            deviceMessageList.add(convert(item));
        });

        return deviceMessageList;
    }

    @Override
    public Long addClientInBoxMsg(String clientId, Long messageId, MessageAckEnum ackEnum) {

        DeviceInboxMessageDO deviceInboxMessageDO = new DeviceInboxMessageDO();
        deviceInboxMessageDO.setAck(ackEnum.getCode());
        deviceInboxMessageDO.setClientId(clientId);
        deviceInboxMessageDO.setMessageId(messageId);
        deviceInboxMessageDO.setStoredTime(new Date());

        DBUtils.operate(new DBCallback() {
            @Override
            public Long operate(SqlSession sqlSession) {
                return DBUtils.getMapper(sqlSession, DBUtils.clientInboxMessageMapperClass).addInboxMessage(deviceInboxMessageDO);
            }
        });
        return deviceInboxMessageDO.getId();
    }

    @Override
    public boolean ackMessage(String clientId, Long messageId) {
        Integer effect = DBUtils.operate(new DBCallback() {
            @Override
            public Integer operate(SqlSession sqlSession) {
                return DBUtils.getMapper(sqlSession, DBUtils.clientInboxMessageMapperClass).ack(clientId,messageId);
            }
        });
        return effect > 0;
    }

    private EventDO convert2Event(DeviceMessage deviceMessage) {
        EventDO eventDO = new EventDO();
        eventDO.setNodeIp(RemotingHelper.getLocalAddr());
        eventDO.setContent(JSONObject.toJSONString(deviceMessage));
        eventDO.setEventCode(ClusterEventCodeEnum.DISPATCHER_CLIENT_MESSAGE.getCode());
        eventDO.setGmtCreate(new Date());
        return eventDO;
    }

    private MessageDO convert(DeviceMessage deviceMessage) {
        MessageDO messageDO = new MessageDO();
        messageDO.setContent(deviceMessage.getContent());
        messageDO.setFromClientId(deviceMessage.getFromClientId());
        messageDO.setSource(deviceMessage.getSource().getCode());
        messageDO.setStoredTime(deviceMessage.getStoredTime());
        messageDO.setTopic(deviceMessage.getTopic());
        if (deviceMessage.getProperties() != null) {
            messageDO.setProperties(JSONObject.toJSONString(deviceMessage.getProperties()));
        }
        return messageDO;
    }

    private DeviceMessage convert(MessageDO messageDO) {
        DeviceMessage deviceMessage = new DeviceMessage();
        deviceMessage.setTopic(messageDO.getTopic());
        deviceMessage.setStoredTime(messageDO.getStoredTime());
        deviceMessage.setSource(MessageSourceEnum.valueOf(messageDO.getSource()));
        deviceMessage.setContent(messageDO.getContent());
        deviceMessage.setId(messageDO.getId());
        deviceMessage.setFromClientId(messageDO.getFromClientId());

        String properties = messageDO.getProperties();
        if (properties != null) {
            deviceMessage.setProperties(JSONObject.parseObject(properties, Map.class));
        }
        return deviceMessage;
    }


    private DeviceInboxMessage convert(DeviceInboxMessageDO deviceInboxMessageDO) {
        DeviceInboxMessage deviceInboxMessage = new DeviceInboxMessage();
        deviceInboxMessage.setAck(deviceInboxMessageDO.getAck());
        deviceInboxMessage.setAckTime(deviceInboxMessageDO.getAckTime());
        deviceInboxMessage.setClientId(deviceInboxMessageDO.getClientId());
        deviceInboxMessage.setId(deviceInboxMessageDO.getId());
        deviceInboxMessage.setMessageId(deviceInboxMessageDO.getMessageId());
        deviceInboxMessage.setStoredTime(deviceInboxMessageDO.getStoredTime());
        return deviceInboxMessage;
    }
}


================================================
FILE: jmqtt-bus/src/main/java/org/jmqtt/bus/impl/DeviceSessionManagerImpl.java
================================================
package org.jmqtt.bus.impl;

import com.alibaba.fastjson.JSONObject;
import org.apache.ibatis.session.SqlSession;
import org.jmqtt.bus.DeviceSessionManager;
import org.jmqtt.bus.enums.DeviceOnlineStateEnum;
import org.jmqtt.bus.enums.TransportProtocolEnum;
import org.jmqtt.bus.model.DeviceSession;
import org.jmqtt.bus.store.DBCallback;
import org.jmqtt.bus.store.DBUtils;
import org.jmqtt.bus.store.daoobject.SessionDO;

import java.util.Date;
import java.util.Map;

public class DeviceSessionManagerImpl implements DeviceSessionManager {

    @Override
    public DeviceSession getSession(String clientId) {

        // optimize: add cache
        SessionDO sessionDO = (SessionDO) DBUtils.operate(new DBCallback() {
            @Override
            public Object operate(SqlSession sqlSession) {
                return DBUtils.getMapper(sqlSession, DBUtils.sessionMapperClass).getSession(clientId);
            }
        });
        if (sessionDO == null) {
            return null;
        }
        return convert(sessionDO);
    }

    @Override
    public void storeSession(DeviceSession deviceSession) {
        SessionDO sessionDO = convert(deviceSession);
        DBUtils.operate(new DBCallback() {
            @Override
            public Long operate(SqlSession sqlSession) {
                return DBUtils.getMapper(sqlSession, DBUtils.sessionMapperClass).storeSession(sessionDO);
            }
        });
    }

    @Override
    public void offline(String clientId) {
        SessionDO sessionDO = new SessionDO();
        sessionDO.setClientId(clientId);
        sessionDO.setOnline(DeviceOnlineStateEnum.OFFLINE.getCode());
        sessionDO.setLastOfflineTime(new Date());
        DBUtils.operate(new DBCallback() {
            @Override
            public Object operate(SqlSession sqlSession) {
                return DBUtils.getMapper(sqlSession, DBUtils.sessionMapperClass).offline(sessionDO);
            }
        });
    }

    private DeviceSession convert(SessionDO sessionDO) {
        DeviceSession deviceSession = new DeviceSession();
        deviceSession.setClientId(sessionDO.getClientId());
        deviceSession.setClientIp(sessionDO.getClientIp());
        deviceSession.setLastOfflineTime(sessionDO.getLastOfflineTime());
        deviceSession.setOnline(DeviceOnlineStateEnum.valueOf(sessionDO.getOnline()));
        deviceSession.setOnlineTime(sessionDO.getOnlineTime());
        String properties = sessionDO.getProperties();
        if (properties != null) {
            deviceSession.setProperties(JSONObject.parseObject(properties, Map.class));
        }
        deviceSession.setServerIp(deviceSession.getServerIp());
        deviceSession.setTransportProtocol(TransportProtocolEnum.valueOf(sessionDO.getTransportProtocol()));
        return deviceSession;
    }

    private SessionDO convert(DeviceSession deviceSession) {
        SessionDO sessionDO = new SessionDO();
        sessionDO.setClientId(deviceSession.getClientId());
        sessionDO.setClientIp(deviceSession.getClientIp());
        sessionDO.setLastOfflineTime(deviceSession.getLastOfflineTime());
        sessionDO.setOnline(deviceSession.getOnline().getCode());
        sessionDO.setOnlineTime(deviceSession.getOnlineTime());
        sessionDO.setProperties(deviceSession.getProperties() == null ? null : JSONObject.toJSONString(deviceSession.getProperties()));
        sessionDO.setServerIp(deviceSession.getServerIp());
        sessionDO.setTransportProtocol(deviceSession.getTransportProtocol().getCode());
        return sessionDO;
    }
}


================================================
FILE: jmqtt-bus/src/main/java/org/jmqtt/bus/impl/DeviceSubscriptionManagerImpl.java
================================================
package org.jmqtt.bus.impl;

import com.alibaba.fastjson.JSONObject;
import org.apache.ibatis.session.SqlSession;
import org.jmqtt.bus.DeviceSubscriptionManager;
import org.jmqtt.bus.model.DeviceSubscription;
import org.jmqtt.bus.store.DBCallback;
import org.jmqtt.bus.store.DBUtils;
import org.jmqtt.bus.store.daoobject.SubscriptionDO;
import org.jmqtt.bus.subscription.SubscriptionMatcher;
import org.jmqtt.bus.subscription.Topic;
import org.jmqtt.bus.subscription.model.Subscription;
import org.jmqtt.support.log.JmqttLogger;
import org.jmqtt.support.log.LogUtil;
import org.slf4j.Logger;

import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;


public class DeviceSubscriptionManagerImpl implements DeviceSubscriptionManager {

    private static final Logger log = JmqttLogger.busLog;

    private SubscriptionMatcher subscriptionMatcher;

    public DeviceSubscriptionManagerImpl(SubscriptionMatcher subscriptionMatcher){
        this.subscriptionMatcher = subscriptionMatcher;
    }

    @Override
    public boolean subscribe(DeviceSubscription deviceSubscription) {
        SqlSession sqlSession = null;
        try {
            sqlSession = DBUtils.getSqlSessionWithTrans();
            SubscriptionDO subscriptionDO = convert(deviceSubscription);
            DBUtils.getMapper(sqlSession,DBUtils.subscriptionMapperClass).storeSubscription(subscriptionDO);
            if (subscriptionDO.getId() == null || subscriptionDO.getId() <= 0) {
                LogUtil.error(log,"[SUBSCRIBE] store subscription fail.");
                return false;
            }
            Subscription subscription = convert2Sub(deviceSubscription);
            boolean addSubTree = subscriptionMatcher.subscribe(subscription);
            if (!addSubTree) {
                LogUtil.error(log,"[SUBSCRIBE] sub tree fail.");
                sqlSession.rollback();
                return false;
            }
            sqlSession.commit();
            return true;
        } catch (Exception ex) {
            LogUtil.error(log,"[SUBSCRIBE] subscribe failure.ex:{}",ex);
            if (sqlSession != null) {
                sqlSession.rollback();
            }
        } finally {
            if (sqlSession != null) {
                sqlSession.close();
            }
        }
        return false;
    }

    @Override
    public boolean unSubscribe(String clientId,String topic) {
        SqlSession sqlSession = null;
        try {
            sqlSession = DBUtils.getSqlSessionWithTrans();
            Integer effectNum = DBUtils.getMapper(sqlSession,DBUtils.subscriptionMapperClass).delSubscription(clientId,topic);
            if (effectNum == null || effectNum <= 0) {
                LogUtil.error(log,"[UNSUBSCRIBE] del subscription fail.");
                return false;
            }
            boolean subSub = subscriptionMatcher.unSubscribe(topic,clientId);
            if (!subSub) {
                LogUtil.error(log,"[SUBSUBSCRIBE] unsub from tree fail.");
                sqlSession.rollback();
                return false;
            }
            sqlSession.commit();
            return true;
        } catch (Exception ex) {
            LogUtil.error(log,"[SUBSCRIBE] subscribe failure.ex:{}",ex);
            if (sqlSession != null) {
                sqlSession.rollback();
            }
        } finally {
            if (sqlSession != null) {
                sqlSession.close();
            }
        }
        return false;
    }

    @Override
    public boolean isMatch(String pubTopic, String subTopic) {
        Topic topic = new Topic(pubTopic);
        return topic.match(new Topic(subTopic));
    }

    @Override
    public boolean onlySubscribe2Tree(DeviceSubscription deviceSubscription) {
        Subscription subscription = convert2Sub(deviceSubscription);
        return subscriptionMatcher.subscribe(subscription);
    }

    @Override
    public boolean onlyUnUnSubscribeFromTree(String clientId, String topic) {
        return subscriptionMatcher.unSubscribe(topic,clientId);
    }

    @Override
    public Set<DeviceSubscription> getAllSubscription(String clientId) {
        List<SubscriptionDO> subscriptionDOS = DBUtils.operate(new DBCallback() {
            @Override
            public List<SubscriptionDO> operate(SqlSession sqlSession) {
                return DBUtils.getMapper(sqlSession,DBUtils.subscriptionMapperClass).getAllSubscription(clientId);
            }
        });
        if (subscriptionDOS == null) {
            return null;
        }
        Set<DeviceSubscription> subscriptions = new HashSet<>(subscriptionDOS.size());
        subscriptionDOS.forEach(item -> {
            subscriptions.add(convert(item));
        });
        return subscriptions;
    }

    @Override
    public void deleteAllSubscription(String clientId) {
        DBUtils.operate(new DBCallback() {
            @Override
            public Integer operate(SqlSession sqlSession) {
                return DBUtils.getMapper(sqlSession,DBUtils.subscriptionMapperClass).clearSubscription(clientId);
            }
        });
    }

    private Subscription convert2Sub(DeviceSubscription deviceSubscription){
        return new Subscription(deviceSubscription.getClientId(),deviceSubscription.getTopic(),deviceSubscription.getProperties());
    }

    private DeviceSubscription convert(SubscriptionDO subscriptionDO){
        DeviceSubscription deviceSubscription = new DeviceSubscription();
        deviceSubscription.setClientId(subscriptionDO.getClientId());
        deviceSubscription.setTopic(subscriptionDO.getTopic());
        deviceSubscription.setSubscribeTime(subscriptionDO.getSubscribeTime());
        if (subscriptionDO.getProperties() != null) {
            deviceSubscription.setProperties(JSONObject.parseObject(subscriptionDO.getProperties(), Map.class));
        }
        return deviceSubscription;
    }

    private SubscriptionDO convert(DeviceSubscription deviceSubscription){
        SubscriptionDO subscriptionDO = new SubscriptionDO();
        subscriptionDO.setClientId(deviceSubscription.getClientId());
        subscriptionDO.setTopic(deviceSubscription.getTopic());
        subscriptionDO.setProperties(deviceSubscription.getProperties() != null? JSONObject.toJSONString(deviceSubscription.getProperties()): null);
        subscriptionDO.setSubscribeTime(deviceSubscription.getSubscribeTime());
        return subscriptionDO;
    }
}


================================================
FILE: jmqtt-bus/src/main/java/org/jmqtt/bus/model/ClusterEvent.java
================================================
package org.jmqtt.bus.model;

import org.jmqtt.bus.enums.ClusterEventCodeEnum;
import org.jmqtt.bus.subscription.model.Subscription;

import java.util.Date;

public class ClusterEvent {

    private Long id;

    private String content;

    private Date gmtCreate;

    private String nodeIp;

    private ClusterEventCodeEnum clusterEventCode;

    /**
     * 待分发消息时的订阅关系
     */
    private Subscription subscription;

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getContent() {
        return content;
    }

    public void setContent(String content) {
        this.content = content;
    }

    public Date getGmtCreate() {
        return gmtCreate;
    }

    public void setGmtCreate(Date gmtCreate) {
        this.gmtCreate = gmtCreate;
    }

    public String getNodeIp() {
        return nodeIp;
    }

    public void setNodeIp(String nodeIp) {
        this.nodeIp = nodeIp;
    }

    public ClusterEventCodeEnum getClusterEventCode() {
        return clusterEventCode;
    }

    public void setClusterEventCode(ClusterEventCodeEnum clusterEventCode) {
        this.clusterEventCode = clusterEventCode;
    }

    public Subscription getSubscription() {
        return subscription;
    }

    public void setSubscription(Subscription subscription) {
        this.subscription = subscription;
    }

    public ClusterEvent clone() {
        ClusterEvent event = new ClusterEvent();
        event.setClusterEventCode(this.getClusterEventCode());
        event.setContent(this.getContent());
        event.setGmtCreate(this.getGmtCreate());
        event.setNodeIp(this.getNodeIp());
        event.setId(this.getId());
        event.setSubscription(this.getSubscription());
        return event;
    }
}


================================================
FILE: jmqtt-bus/src/main/java/org/jmqtt/bus/model/DeviceInboxMessage.java
================================================
package org.jmqtt.bus.model;

import java.util.Date;

public class DeviceInboxMessage {

    private Long id;

    private String clientId;

    private Long messageId;

    private Integer ack;

    private Date storedTime;

    private Date ackTime;

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getClientId() {
        return clientId;
    }

    public void setClientId(String clientId) {
        this.clientId = clientId;
    }

    public Long getMessageId() {
        return messageId;
    }

    public void setMessageId(Long messageId) {
        this.messageId = messageId;
    }

    public Integer getAck() {
        return ack;
    }

    public void setAck(Integer ack) {
        this.ack = ack;
    }

    public Date getStoredTime() {
        return storedTime;
    }

    public void setStoredTime(Date storedTime) {
        this.storedTime = storedTime;
    }

    public Date getAckTime() {
        return ackTime;
    }

    public void setAckTime(Date ackTime) {
        this.ackTime = ackTime;
    }
}


================================================
FILE: jmqtt-bus/src/main/java/org/jmqtt/bus/model/DeviceMessage.java
================================================

package org.jmqtt.bus.model;

import org.jmqtt.bus.enums.MessageSourceEnum;

import java.util.Date;
import java.util.Map;

public class DeviceMessage {

    private Long id;

    private byte[] content;

    private MessageSourceEnum source;

    private String fromClientId;

    private Date storedTime;

    private String topic;

    private Map<String,Object> properties;

    public <T> T  getProperty(String key){
        if (properties == null || properties.containsKey(key)) {
            return (T) properties.get(key);
        }
        return null;
    }

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public byte[] getContent() {
        return content;
    }

    public void setContent(byte[] content) {
        this.content = content;
    }

    public MessageSourceEnum getSource() {
        return source;
    }

    public void setSource(MessageSourceEnum source) {
        this.source = source;
    }

    public Date getStoredTime() {
        return storedTime;
    }

    public void setStoredTime(Date storedTime) {
        this.storedTime = storedTime;
    }

    public String getTopic() {
        return topic;
    }

    public void setTopic(String topic) {
        this.topic = topic;
    }

    public Map<String, Object> getProperties() {
        return properties;
    }

    public void setProperties(Map<String, Object> properties) {
        this.properties = properties;
    }

    public String getFromClientId() {
        return fromClientId;
    }

    public void setFromClientId(String fromClientId) {
        this.fromClientId = fromClientId;
    }
}


================================================
FILE: jmqtt-bus/src/main/java/org/jmqtt/bus/model/DeviceSession.java
================================================

package org.jmqtt.bus.model;

import org.jmqtt.bus.enums.DeviceOnlineStateEnum;
import org.jmqtt.bus.enums.TransportProtocolEnum;

import java.util.Date;
import java.util.Map;

/**
 * session
 */
public class DeviceSession {

    private String clientId;

    private TransportProtocolEnum transportProtocol;

    private String clientIp;

    private String serverIp;

    private DeviceOnlineStateEnum online;

    private Date onlineTime;

    private Date lastOfflineTime;

    private Map<String,Object> properties;

    public String getClientId() {
        return clientId;
    }

    public void setClientId(String clientId) {
        this.clientId = clientId;
    }

    public TransportProtocolEnum getTransportProtocol() {
        return transportProtocol;
    }

    public void setTransportProtocol(TransportProtocolEnum transportProtocol) {
        this.transportProtocol = transportProtocol;
    }

    public String getClientIp() {
        return clientIp;
    }

    public void setClientIp(String clientIp) {
        this.clientIp = clientIp;
    }

    public String getServerIp() {
        return serverIp;
    }

    public void setServerIp(String serverIp) {
        this.serverIp = serverIp;
    }

    public DeviceOnlineStateEnum getOnline() {
        return online;
    }

    public void setOnline(DeviceOnlineStateEnum online) {
        this.online = online;
    }

    public Map<String, Object> getProperties() {
        return properties;
    }

    public void setProperties(Map<String, Object> properties) {
        this.properties = properties;
    }

    public Date getOnlineTime() {
        return onlineTime;
    }

    public void setOnlineTime(Date onlineTime) {
        this.onlineTime = onlineTime;
    }

    public Date getLastOfflineTime() {
        return lastOfflineTime;
    }

    public void setLastOfflineTime(Date lastOfflineTime) {
        this.lastOfflineTime = lastOfflineTime;
    }
}


================================================
FILE: jmqtt-bus/src/main/java/org/jmqtt/bus/model/DeviceSubscription.java
================================================
package org.jmqtt.bus.model;

import java.util.Date;
import java.util.Map;

public class DeviceSubscription {

    private String clientId;

    private String topic;

    private Date subscribeTime;

    private Map<String,Object> properties;


    public <T> T getProperty(String key){
        if (properties == null) {
            return null;
        }
        return (T) properties.get(key);
    }

    public String getClientId() {
        return clientId;
    }

    public void setClientId(String clientId) {
        this.clientId = clientId;
    }

    public String getTopic() {
        return topic;
    }

    public void setTopic(String topic) {
        this.topic = topic;
    }

    public Date getSubscribeTime() {
        return subscribeTime;
    }

    public void setSubscribeTime(Date subscribeTime) {
        this.subscribeTime = subscribeTime;
    }

    public Map<String, Object> getProperties() {
        return properties;
    }

    public void setProperties(Map<String, Object> properties) {
        this.properties = properties;
    }
}


================================================
FILE: jmqtt-bus/src/main/java/org/jmqtt/bus/store/AbstractDBStore.java
================================================
package org.jmqtt.bus.store;

import org.apache.ibatis.session.SqlSession;
import org.jmqtt.bus.store.mapper.*;
import org.jmqtt.support.config.BrokerConfig;
import org.jmqtt.support.log.JmqttLogger;
import org.slf4j.Logger;

public abstract class AbstractDBStore {



    protected final static Logger log = JmqttLogger.storeLog;

    protected void start(BrokerConfig brokerConfig) {
        DBUtils.getInstance().start(brokerConfig);
    }

    protected void shutdown() {
        DBUtils.getInstance().shutdown();
    }

    protected <T> T getMapper(SqlSession sqlSession,Class<T> clazz) {
        return sqlSession.getMapper(clazz);
    }

    protected Object operate(DBCallback dbCallback){
       return DBUtils.getInstance().operate(dbCallback);
    }

    public SqlSession getSqlSessionWithTrans() {
        return DBUtils.getInstance().getSqlSessionWithTrans();
    }
}


================================================
FILE: jmqtt-bus/src/main/java/org/jmqtt/bus/store/DBCallback.java
================================================
package org.jmqtt.bus.store;

import org.apache.ibatis.session.SqlSession;

public interface DBCallback {
    <T> T operate(SqlSession sqlSession);
}


================================================
FILE: jmqtt-bus/src/main/java/org/jmqtt/bus/store/DBUtils.java
================================================
package org.jmqtt.bus.store;

import com.alibaba.druid.pool.DruidDataSource;
import org.apache.ibatis.datasource.DataSourceFactory;
import org.apache.ibatis.mapping.Environment;
import org.apache.ibatis.session.Configuration;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import org.apache.ibatis.transaction.TransactionFactory;
import org.apache.ibatis.transaction.jdbc.JdbcTransactionFactory;
import org.jmqtt.bus.store.mapper.*;
import org.jmqtt.support.config.BrokerConfig;
import org.jmqtt.support.log.JmqttLogger;
import org.jmqtt.support.log.LogUtil;
import org.slf4j.Logger;

import javax.sql.DataSource;
import java.sql.SQLException;
import java.util.Properties;
import java.util.concurrent.atomic.AtomicBoolean;

/**
 * db 工具类
 */
public class DBUtils {

    private static final Logger log = JmqttLogger.storeLog;

    public static final Class<SessionMapper>            sessionMapperClass           = SessionMapper.class;
    public static final Class<SubscriptionMapper>       subscriptionMapperClass      = SubscriptionMapper.class;
    public static final Class<EventMapper>              eventMapperClass             = EventMapper.class;
    public static final Class<ClientInboxMessageMapper> clientInboxMessageMapperClass     = ClientInboxMessageMapper.class;
    public static final Class<MessageMapper>            messageMapperClass    = MessageMapper.class;
    public static final Class<RetainMessageMapper>      retainMessageMapperClass     = RetainMessageMapper.class;

    private static final DBUtils dbUtils = new DBUtils();

    private DBUtils(){}

    private SqlSessionFactory sqlSessionFactory;

    private  AtomicBoolean start = new AtomicBoolean(false);

    public static DBUtils getInstance(){
        return dbUtils;
    }

    public void start(BrokerConfig brokerConfig){
        if (this.start.compareAndSet(false,true)) {
            LogUtil.info(log,"DB store start...");
            DataSource dataSource = new DataSourceFactory() {
                @Override
                public void setProperties(Properties properties) {
                }

                @Override
                public DataSource getDataSource() {
                    DruidDataSource dds = new DruidDataSource();
                    dds.setDriverClassName(brokerConfig.getDriver());
                    dds.setUrl(brokerConfig.getUrl());
                    dds.setUsername(brokerConfig.getUsername());
                    dds.setPassword(brokerConfig.getPassword());
                    // 其他配置可自行补充
                    dds.setKeepAlive(true);
                    dds.setMinEvictableIdleTimeMillis(180000);
                    dds.setMaxWait(10*1000);
                    dds.setInitialSize(5);
                    dds.setMinIdle(5);
                    try {
                        dds.init();
                    } catch (SQLException e) {
                        e.printStackTrace();
                        System.exit(-1);
                    }
                    return dds;
                }
            }.getDataSource();

            TransactionFactory transactionFactory = new JdbcTransactionFactory();
            Environment environment = new Environment("development", transactionFactory, dataSource);
            Configuration configuration = new Configuration(environment);

            // 初始化所有mapper
            configuration.addMapper(SessionMapper.class);
            configuration.addMapper(SubscriptionMapper.class);
            configuration.addMapper(MessageMapper.class);
            configuration.addMapper(EventMapper.class);
            configuration.addMapper(ClientInboxMessageMapper.class);
            configuration.addMapper(RetainMessageMapper.class);

            configuration.setMapUnderscoreToCamelCase(true);
            this.sqlSessionFactory = new SqlSessionFactoryBuilder().build(configuration);
            LogUtil.info(log,"DB store start success...");
        }
    }

    public void shutdown(){}


    public static final <T> T operate(DBCallback dbCallback) {
        try (SqlSession sqlSession = DBUtils.getInstance().sqlSessionFactory.openSession(true)){
            return dbCallback.operate(sqlSession);
        }
    }

    public static final <T> T getMapper(SqlSession sqlSession,Class<T> clazz) {
        return sqlSession.getMapper(clazz);
    }

    /**
     * 获取关闭事物的session,需要手动提交事物
     */
    public static final SqlSession getSqlSessionWithTrans() {
        SqlSession sqlSession = DBUtils.getInstance().sqlSessionFactory.openSession(false);
        return sqlSession;
    }


}


================================================
FILE: jmqtt-bus/src/main/java/org/jmqtt/bus/store/daoobject/DeviceInboxMessageDO.java
================================================
package org.jmqtt.bus.store.daoobject;

import java.io.Serializable;
import java.util.Date;

public class DeviceInboxMessageDO implements Serializable {

    private static final long serialVersionUID = 143413131231231L;

    private Long id;

    private String clientId;

    private Long messageId;

    private Integer ack;

    private Date storedTime;

    private Date ackTime;

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getClientId() {
        return clientId;
    }

    public void setClientId(String clientId) {
        this.clientId = clientId;
    }

    public Long getMessageId() {
        return messageId;
    }

    public void setMessageId(Long messageId) {
        this.messageId = messageId;
    }

    public Integer getAck() {
        return ack;
    }

    public void setAck(Integer ack) {
        this.ack = ack;
    }

    public Date getStoredTime() {
        return storedTime;
    }

    public void setStoredTime(Date storedTime) {
        this.storedTime = storedTime;
    }

    public Date getAckTime() {
        return ackTime;
    }

    public void setAckTime(Date ackTime) {
        this.ackTime = ackTime;
    }
}


================================================
FILE: jmqtt-bus/src/main/java/org/jmqtt/bus/store/daoobject/EventDO.java
================================================
package org.jmqtt.bus.store.daoobject;

import java.io.Serializable;
import java.util.Date;

public class EventDO implements Serializable {

    private static final long serialVersionUID = 12213213131231231L;

    private Long id;

    private String content;

    private Date gmtCreate;

    private String nodeIp;

    private String eventCode;

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getContent() {
        return content;
    }

    public void setContent(String content) {
        this.content = content;
    }

    public Date getGmtCreate() {
        return gmtCreate;
    }

    public void setGmtCreate(Date gmtCreate) {
        this.gmtCreate = gmtCreate;
    }

    public String getNodeIp() {
        return nodeIp;
    }

    public void setNodeIp(String nodeIp) {
        this.nodeIp = nodeIp;
    }

    public String getEventCode() {
        return eventCode;
    }

    public void setEventCode(String eventCode) {
        this.eventCode = eventCode;
    }
}


================================================
FILE: jmqtt-bus/src/main/java/org/jmqtt/bus/store/daoobject/MessageDO.java
================================================
package org.jmqtt.bus.store.daoobject;

import java.io.Serializable;
import java.util.Date;

public class MessageDO implements Serializable {

    private static final long serialVersionUID = 12313131231231L;

    private Long id;

    private String source;

    private byte[] content;

    private String topic;

    private String fromClientId;

    private Date storedTime;

    private String properties;

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getSource() {
        return source;
    }

    public void setSource(String source) {
        this.source = source;
    }

    public byte[] getContent() {
        return content;
    }

    public void setContent(byte[] content) {
        this.content = content;
    }

    public String getTopic() {
        return topic;
    }

    public void setTopic(String topic) {
        this.topic = topic;
    }

    public String getFromClientId() {
        return fromClientId;
    }

    public void setFromClientId(String fromClientId) {
        this.fromClientId = fromClientId;
    }

    public Date getStoredTime() {
        return storedTime;
    }

    public void setStoredTime(Date storedTime) {
        this.storedTime = storedTime;
    }

    public String getProperties() {
        return properties;
    }

    public void setProperties(String properties) {
        this.properties = properties;
    }
}


================================================
FILE: jmqtt-bus/src/main/java/org/jmqtt/bus/store/daoobject/RetainMessageDO.java
================================================
package org.jmqtt.bus.store.daoobject;

import java.io.Serializable;
import java.util.Date;

public class RetainMessageDO implements Serializable {

    private static final long serialVersionUID = 12213131231231L;

    private Long id;

    private String topic;

    private byte[] content;

    private String fromClientId;

    private Date storedTime;

    private String properties;

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getTopic() {
        return topic;
    }

    public void setTopic(String topic) {
        this.topic = topic;
    }

    public byte[] getContent() {
        return content;
    }

    public void setContent(byte[] content) {
        this.content = content;
    }

    public String getFromClientId() {
        return fromClientId;
    }

    public void setFromClientId(String fromClientId) {
        this.fromClientId = fromClientId;
    }

    public Date getStoredTime() {
        return storedTime;
    }

    public void setStoredTime(Date storedTime) {
        this.storedTime = storedTime;
    }

    public String getProperties() {
        return properties;
    }

    public void setProperties(String properties) {
        this.properties = properties;
    }
}


================================================
FILE: jmqtt-bus/src/main/java/org/jmqtt/bus/store/daoobject/SessionDO.java
================================================

package org.jmqtt.bus.store.daoobject;

import java.io.Serializable;
import java.util.Date;

public class SessionDO implements Serializable {

    private static final long serialVersionUID = 12213131231231L;

    private Long id;

    private String clientId;

    private String online;

    private String transportProtocol;

    private String clientIp;

    private String serverIp;

    private Date lastOfflineTime;

    private Date onlineTime;

    private String properties;

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getClientId() {
        return clientId;
    }

    public void setClientId(String clientId) {
        this.clientId = clientId;
    }

    public String getOnline() {
        return online;
    }

    public void setOnline(String online) {
        this.online = online;
    }

    public String getTransportProtocol() {
        return transportProtocol;
    }

    public void setTransportProtocol(String transportProtocol) {
        this.transportProtocol = transportProtocol;
    }

    public String getClientIp() {
        return clientIp;
    }

    public void setClientIp(String clientIp) {
        this.clientIp = clientIp;
    }

    public String getServerIp() {
        return serverIp;
    }

    public void setServerIp(String serverIp) {
        this.serverIp = serverIp;
    }

    public Date getLastOfflineTime() {
        return lastOfflineTime;
    }

    public void setLastOfflineTime(Date lastOfflineTime) {
        this.lastOfflineTime = lastOfflineTime;
    }

    public Date getOnlineTime() {
        return onlineTime;
    }

    public void setOnlineTime(Date onlineTime) {
        this.onlineTime = onlineTime;
    }

    public String getProperties() {
        return properties;
    }

    public void setProperties(String properties) {
        this.properties = properties;
    }
}


================================================
FILE: jmqtt-bus/src/main/java/org/jmqtt/bus/store/daoobject/SubscriptionDO.java
================================================
package org.jmqtt.bus.store.daoobject;

import java.io.Serializable;
import java.util.Date;

public class SubscriptionDO implements Serializable {

    private static final long serialVersionUID = 12213131231231L;

    private Long id;

    private String clientId;

    private String topic;

    private Date subscribeTime;

    private String properties;

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getClientId() {
        return clientId;
    }

    public void setClientId(String clientId) {
        this.clientId = clientId;
    }

    public String getTopic() {
        return topic;
    }

    public void setTopic(String topic) {
        this.topic = topic;
    }

    public Date getSubscribeTime() {
        return subscribeTime;
    }

    public void setSubscribeTime(Date subscribeTime) {
        this.subscribeTime = subscribeTime;
    }

    public String getProperties() {
        return properties;
    }

    public void setProperties(String properties) {
        this.properties = properties;
    }
}


================================================
FILE: jmqtt-bus/src/main/java/org/jmqtt/bus/store/mapper/ClientInboxMessageMapper.java
================================================
package org.jmqtt.bus.store.mapper;

import org.apache.ibatis.annotations.*;
import org.jmqtt.bus.store.daoobject.DeviceInboxMessageDO;

import java.util.List;

public interface ClientInboxMessageMapper {

    @Insert("INSERT INTO jmqtt_client_inbox(client_id,message_id,ack,stored_time) VALUES (#{clientId},#{messageId},#{ack},#{storedTime})")
    @Options(useGeneratedKeys=true,keyProperty="id")
    Long addInboxMessage(DeviceInboxMessageDO deviceInboxMessageDO);

    @Update("UPDATE jmqtt_client_inbox set ack = 1,ack_time = now()"
            + "WHERE client_id = #{clientId} and message_id = #{messageId}")
    Integer ack(@Param("clientId") String clientId,@Param("messageId") Long messageId);


    @Update("DELETE FROM jmqtt_client_inbox WHERE client_id = #{clientId} AND ack = 0")
    Integer truncateUnAck(@Param("clientId") String clientId);

    @Select("SELECT * FROM jmqtt_client_inbox WHERE client_id = #{clientId} and ack = 0 ORDER BY stored_time ASC LIMIT #{limit} ")
    List<DeviceInboxMessageDO> getUnAckMessages(@Param("clientId") String clientId, @Param("limit") int limit);
}


================================================
FILE: jmqtt-bus/src/main/java/org/jmqtt/bus/store/mapper/EventMapper.java
================================================
package org.jmqtt.bus.store.mapper;

import org.apache.ibatis.annotations.Insert;
import org.apache.ibatis.annotations.Options;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Select;
import org.jmqtt.bus.store.daoobject.EventDO;

import java.util.List;

public interface EventMapper {

    @Insert("insert into jmqtt_cluster_event (content,gmt_create,node_ip,event_code) values "
            + "(#{content},#{gmtCreate},#{nodeIp},#{eventCode})")
    @Options(useGeneratedKeys=true,keyProperty="id")
    Long sendEvent(EventDO eventDO);


    @Select("select id,content,gmt_create,node_ip,event_code from jmqtt_cluster_event "
            + "where id > #{offset} order by id asc limit #{maxNum}")
    List<EventDO> consumeEvent(@Param("offset") long offset, @Param("maxNum") int maxNum);

    @Select("SELECT max(id) FROM jmqtt_cluster_event")
    Long getMaxOffset();
}


================================================
FILE: jmqtt-bus/src/main/java/org/jmqtt/bus/store/mapper/MessageMapper.java
================================================
package org.jmqtt.bus.store.mapper;

import org.apache.ibatis.annotations.Insert;
import org.apache.ibatis.annotations.Options;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Select;
import org.jmqtt.bus.store.daoobject.MessageDO;

import java.util.List;

public interface MessageMapper {

    @Insert("INSERT INTO jmqtt_message(id,source,content,topic,from_client_id,stored_time,properties) VALUES(#{id},#{source},#{content}"
            + ",#{topic},#{fromClientId},#{storedTime},#{properties})")
    @Options(useGeneratedKeys=true,keyProperty="id")
    Long storeMessage(MessageDO messageDO);

    @Select("SELECT id,source,content,topic,from_client_id,stored_time,properties FROM jmqtt_message WHERE id = #{id}")
    MessageDO getMessage(@Param("id") long id);

    @Select("<script>SELECT id,source,content,topic,from_client_id,stored_time,properties FROM jmqtt_message "
            + "where id in "
            + "<foreach collection='ids' item='id' open='(' separator=',' close=')'>"
            + "#{id}"
            + "</foreach>  "
            + "</script>")
    List<MessageDO> queryMessageByIds(@Param("ids")List<Long> ids);
}


================================================
FILE: jmqtt-bus/src/main/java/org/jmqtt/bus/store/mapper/RetainMessageMapper.java
================================================
package org.jmqtt.bus.store.mapper;

import org.apache.ibatis.annotations.Delete;
import org.apache.ibatis.annotations.Insert;
import org.apache.ibatis.annotations.Options;
import org.apache.ibatis.annotations.Select;
import org.jmqtt.bus.store.daoobject.RetainMessageDO;

import java.util.List;

public interface RetainMessageMapper {

    @Insert("INSERT INTO jmqtt_retain_message(topic,content,from_client_id,stored_time,properties) VALUES"
            + "(#{topic},#{content},#{fromClientId},#{storedTime},#{properties})"
            + " on DUPLICATE key update content = #{content},from_client_id = #{fromClientId},stored_time=#{storedTime},properties = #{properties}")
    @Options(useGeneratedKeys=true,keyProperty="id")
    Long storeRetainMessage(RetainMessageDO retainMessageDO);

    @Select("SELECT topic,content,from_client_id,stored_time,properties FROM jmqtt_retain_message")
    List<RetainMessageDO> getAllRetainMessage();

    @Delete("DELETE FROM jmqtt_retain_message WHERE topic = #{topic}")
    Integer delRetainMessage(String topic);
}


================================================
FILE: jmqtt-bus/src/main/java/org/jmqtt/bus/store/mapper/SessionMapper.java
================================================

package org.jmqtt.bus.store.mapper;

import org.apache.ibatis.annotations.Insert;
import org.apache.ibatis.annotations.Options;
import org.apache.ibatis.annotations.Select;
import org.apache.ibatis.annotations.Update;
import org.jmqtt.bus.store.daoobject.SessionDO;

public interface SessionMapper {

    @Select("select client_id,online,transport_protocol,client_ip,server_ip,last_offline_time,online_time,properties from jmqtt_session where client_id = #{clientId}")
    SessionDO getSession(String clientId);

    @Insert("insert into jmqtt_session(client_id,online,transport_protocol,client_ip,server_ip,last_offline_time,online_time,properties) values "
            + "(#{clientId},#{online},#{transportProtocol},#{clientIp},#{serverIp},#{lastOfflineTime},#{onlineTime},#{properties}) "
            + "on DUPLICATE key update online = #{online},last_offline_time = #{lastOfflineTime},online_time = #{onlineTime}"
            + ",client_ip=#{clientIp},server_ip=#{serverIp},transport_protocol=#{transportProtocol}")
    @Options(useGeneratedKeys=true,keyProperty="id")
    Long storeSession(SessionDO sessionDO);

    @Update("update jmqtt_session set last_offline_time = #{lastOfflineTime},online = #{online} where "
            + "client_id = #{clientId}")
    Long offline(SessionDO sessionDO);

}


================================================
FILE: jmqtt-bus/src/main/java/org/jmqtt/bus/store/mapper/SubscriptionMapper.java
================================================
package org.jmqtt.bus.store.mapper;

import org.apache.ibatis.annotations.*;
import org.jmqtt.bus.store.daoobject.SubscriptionDO;

import java.util.List;

public interface SubscriptionMapper {

    @Insert("INSERT INTO jmqtt_subscription (client_id,topic,properties,subscribe_time) "
            + "VALUES (#{clientId},#{topic},#{properties},#{subscribeTime}) on duplicate key update properties = #{properties},subscribe_time=#{subscribeTime}")
    @Options(useGeneratedKeys=true,keyProperty="id")
    Long storeSubscription(SubscriptionDO subscriptionDO);

    @Delete("DELETE FROM jmqtt_subscription WHERE client_id = #{clientId}")
    Integer clearSubscription(String clientId);

    @Delete("DELETE FROM jmqtt_subscription WHERE client_id = #{clientId} AND topic = #{topic}")
    Integer delSubscription(@Param("clientId") String clientId, @Param("topic") String topic);

    @Select("SELECT id,client_id,topic,properties,subscribe_time FROM jmqtt_subscription WHERE client_id = #{clientId}")
    List<SubscriptionDO> getAllSubscription(String clientId);
}


================================================
FILE: jmqtt-bus/src/main/java/org/jmqtt/bus/subscription/CNode.java
================================================
package org.jmqtt.bus.subscription;

import org.jmqtt.bus.subscription.model.Subscription;

import java.util.*;

public class CNode {

    private Token       token;
    private List<INode> children;
    Set<Subscription> subscriptions;

    CNode() {
        this.children = new ArrayList<>();
        this.subscriptions = new HashSet<>();
    }

    //Copy constructor
    private CNode(Token token, List<INode> children, Set<Subscription> subscriptions) {
        this.token = token; // keep reference, root comparison in directory logic relies on it for now.
        this.subscriptions = new HashSet<>(subscriptions);
        this.children = new ArrayList<>(children);
    }

    public Token getToken() {
        return token;
    }

    public void setToken(Token token) {
        this.token = token;
    }

    boolean anyChildrenMatch(Token token) {
        for (INode iNode : children) {
            final CNode child = iNode.mainNode();
            if (child.equalsToken(token)) {
                return true;
            }
        }
        return false;
    }

    List<INode> allChildren() {
        return this.children;
    }

    INode childOf(Token token) {
        for (INode iNode : children) {
            final CNode child = iNode.mainNode();
            if (child.equalsToken(token)) {
                return iNode;
            }
        }
        throw new IllegalArgumentException("Asked for a token that doesn't exists in any child [" + token + "]");
    }

    private boolean equalsToken(Token token) {
        return token != null && this.token != null && this.token.equals(token);
    }

    @Override
    public int hashCode() {
        return Objects.hash(token);
    }

    CNode copy() {
        return new CNode(this.token, this.children, this.subscriptions);
    }

    public void add(INode newINode) {
        this.children.add(newINode);
    }

    public void remove(INode node) {
        this.children.remove(node);
    }

    CNode addSubscription(Subscription newSubscription) {
        // if already contains one with same topic and same client, keep that with higher QoS
        if (subscriptions.contains(newSubscription)) {
            final Subscription existing = subscriptions.stream()
                    .filter(s -> s.equals(newSubscription))
                    .findFirst().get();
            subscriptions.remove(existing);
            subscriptions.add(new Subscription(newSubscription));
        } else {
            this.subscriptions.add(new Subscription(newSubscription));
        }
        return this;
    }

    boolean containsOnly(String clientId) {
        for (Subscription sub : this.subscriptions) {
            if (!sub.getClientId().equals(clientId)) {
                return false;
            }
        }
        return !this.subscriptions.isEmpty();
    }

    public boolean contains(String clientId) {
        for (Subscription sub : this.subscriptions) {
            if (sub.getClientId().equals(clientId)) {
                return true;
            }
        }
        return false;
    }

    void removeSubscriptionsFor(String clientId) {
        Set<Subscription> toRemove = new HashSet<>();
        for (Subscription sub : this.subscriptions) {
            if (sub.getClientId().equals(clientId)) {
                toRemove.add(sub);
            }
        }
        this.subscriptions.removeAll(toRemove);
    }
}


================================================
FILE: jmqtt-bus/src/main/java/org/jmqtt/bus/subscription/CTrie.java
================================================
package org.jmqtt.bus.subscription;

import org.jmqtt.bus.subscription.model.Subscription;

import java.util.Collections;
import java.util.HashSet;
import java.util.Optional;
import java.util.Set;

public class CTrie {

    interface IVisitor<T> {

        void visit(CNode node, int deep);

        T getResult();
    }

    private static final Token ROOT = new Token("root");
    private static final INode NO_PARENT = null;

    private enum Action {
        OK, REPEAT
    }

    INode root;

    public CTrie() {
        final CNode mainNode = new CNode();
        mainNode.setToken(ROOT);
        this.root = new INode(mainNode);
    }

    public Optional<CNode> lookup(Topic topic) {
        INode inode = this.root;
        Token token = topic.headToken();
        while (!topic.isEmpty() && (inode.mainNode().anyChildrenMatch(token))) {
            topic = topic.exceptHeadToken();
            inode = inode.mainNode().childOf(token);
            token = topic.headToken();
        }
        if (inode == null || !topic.isEmpty()) {
            return Optional.empty();
        }
        return Optional.of(inode.mainNode());
    }

    enum NavigationAction {
        MATCH, GODEEP, STOP
    }

    private NavigationAction evaluate(Topic topic, CNode cnode) {
        if (Token.MULTI.equals(cnode.getToken())) {
            return NavigationAction.MATCH;
        }
        if (topic.isEmpty()) {
            return NavigationAction.STOP;
        }
        final Token token = topic.headToken();
        if (!(Token.SINGLE.equals(cnode.getToken()) || cnode.getToken().equals(token) || ROOT.equals(cnode.getToken()))) {
            return NavigationAction.STOP;
        }
        return NavigationAction.GODEEP;
    }

    public Set<Subscription> recursiveMatch(Topic topic) {
        return recursiveMatch(topic, this.root);
    }

    private Set<Subscription> recursiveMatch(Topic topic, INode inode) {
        CNode cnode = inode.mainNode();
        if (cnode instanceof TNode) {
            return Collections.emptySet();
        }
        NavigationAction action = evaluate(topic, cnode);
        if (action == NavigationAction.MATCH) {
            return cnode.subscriptions;
        }
        if (action == NavigationAction.STOP) {
            return Collections.emptySet();
        }
        Topic remainingTopic = (ROOT.equals(cnode.getToken())) ? topic : topic.exceptHeadToken();
        Set<Subscription> subscriptions = new HashSet<>();
        if (remainingTopic.isEmpty()) {
            subscriptions.addAll(cnode.subscriptions);
        }
        for (INode subInode : cnode.allChildren()) {
            subscriptions.addAll(recursiveMatch(remainingTopic, subInode));
        }
        return subscriptions;
    }

    public void addToTree(Subscription newSubscription) {
        Action res;
        do {
            res = insert(new Topic(newSubscription.getTopic()), this.root, newSubscription);
        } while (res == Action.REPEAT);
    }

    private Action insert(Topic topic, final INode inode, Subscription newSubscription) {
        Token token = topic.headToken();
        if (!topic.isEmpty() && inode.mainNode().anyChildrenMatch(token)) {
            Topic remainingTopic = topic.exceptHeadToken();
            INode nextInode = inode.mainNode().childOf(token);
            return insert(remainingTopic, nextInode, newSubscription);
        } else {
            if (topic.isEmpty()) {
                return insertSubscription(inode, newSubscription);
            } else {
                return createNodeAndInsertSubscription(topic, inode, newSubscription);
            }
        }
    }

    private Action insertSubscription(INode inode, Subscription newSubscription) {
        CNode cnode = inode.mainNode();
        CNode updatedCnode = cnode.copy().addSubscription(newSubscription);
        if (inode.compareAndSet(cnode, updatedCnode)) {
            return Action.OK;
        } else {
            return Action.REPEAT;
        }
    }

    private Action createNodeAndInsertSubscription(Topic topic, INode inode, Subscription newSubscription) {
        INode newInode = createPathRec(topic, newSubscription);
        CNode cnode = inode.mainNode();
        CNode updatedCnode = cnode.copy();
        updatedCnode.add(newInode);

        return inode.compareAndSet(cnode, updatedCnode) ? Action.OK : Action.REPEAT;
    }

    private INode createPathRec(Topic topic, Subscription newSubscription) {
        Topic remainingTopic = topic.exceptHeadToken();
        if (!remainingTopic.isEmpty()) {
            INode inode = createPathRec(remainingTopic, newSubscription);
            CNode cnode = new CNode();
            cnode.setToken(topic.headToken());
            cnode.add(inode);
            return new INode(cnode);
        } else {
            return createLeafNodes(topic.headToken(), newSubscription);
        }
    }

    private INode createLeafNodes(Token token, Subscription newSubscription) {
        CNode newLeafCnode = new CNode();
        newLeafCnode.setToken(token);
        newLeafCnode.addSubscription(newSubscription);

        return new INode(newLeafCnode);
    }

    public void removeFromTree(Topic topic, String clientID) {
        Action res;
        do {
            res = remove(clientID, topic, this.root, NO_PARENT);
        } while (res == Action.REPEAT);
    }

    private Action remove(String clientId, Topic topic, INode inode, INode iParent) {
        Token token = topic.headToken();
        if (!topic.isEmpty() && (inode.mainNode().anyChildrenMatch(token))) {
            Topic remainingTopic = topic.exceptHeadToken();
            INode nextInode = inode.mainNode().childOf(token);
            return remove(clientId, remainingTopic, nextInode, inode);
        } else {
            final CNode cnode = inode.mainNode();
            if (cnode instanceof TNode) {
                // this inode is a tomb, has no clients and should be cleaned up
                // Because we implemented cleanTomb below, this should be rare, but possible
                // Consider calling cleanTomb here too
                return Action.OK;
            }
            if (cnode.containsOnly(clientId) && topic.isEmpty() && cnode.allChildren().isEmpty()) {
                // last client to leave this node, AND there are no downstream children, remove via TNode tomb
                if (inode == this.root) {
                    return inode.compareAndSet(cnode, inode.mainNode().copy()) ? Action.OK : Action.REPEAT;
                }
                TNode tnode = new TNode();
                return inode.compareAndSet(cnode, tnode) ? cleanTomb(inode, iParent) : Action.REPEAT;
            } else if (cnode.contains(clientId) && topic.isEmpty()) {
                CNode updatedCnode = cnode.copy();
                updatedCnode.removeSubscriptionsFor(clientId);
                return inode.compareAndSet(cnode, updatedCnode) ? Action.OK : Action.REPEAT;
            } else {
                //someone else already removed
                return Action.OK;
            }
        }
    }

    /**
     *
     * Cleans Disposes of TNode in separate Atomic CAS operation per
     * http://bravenewgeek.com/breaking-and-entering-lose-the-lock-while-embracing-concurrency/
     *
     * We roughly follow this theory above, but we allow CNode with no Subscriptions to linger (for now).
     *
     *
     * @param inode inode that handle to the tomb node.
     * @param iParent inode parent.
     * @return REPEAT if the this methods wasn't successful or OK.
     */
    private Action cleanTomb(INode inode, INode iParent) {
        CNode updatedCnode = iParent.mainNode().copy();
        updatedCnode.remove(inode);
        return iParent.compareAndSet(iParent.mainNode(), updatedCnode) ? Action.OK : Action.REPEAT;
    }

    public int size() {
        SubscriptionCounterVisitor visitor = new SubscriptionCounterVisitor();
        dfsVisit(this.root, visitor, 0);
        return visitor.getResult();
    }

    public String dumpTree() {
        DumpTreeVisitor visitor = new DumpTreeVisitor();
        dfsVisit(this.root, visitor, 0);
        return visitor.getResult();
    }

    private void dfsVisit(INode node, IVisitor<?> visitor, int deep) {
        if (node == null) {
            return;
        }

        visitor.visit(node.mainNode(), deep);
        ++deep;
        for (INode child : node.mainNode().allChildren()) {
            dfsVisit(child, visitor, deep);
        }
    }
}


================================================
FILE: jmqtt-bus/src/main/java/org/jmqtt/bus/subscription/CTrieSubscriptionMatcher.java
================================================
package org.jmqtt.bus.subscription;

import org.jmqtt.bus.subscription.model.Subscription;
import org.jmqtt.support.log.JmqttLogger;
import org.slf4j.Logger;

import java.util.Set;

public class CTrieSubscriptionMatcher implements SubscriptionMatcher {

    private static final Logger log = JmqttLogger.busLog;

    private CTrie ctrie;

    public CTrieSubscriptionMatcher(){
        this.ctrie = new CTrie();
    }

    @Override
    public Set<Subscription> match(String topicFilter) {
        Topic topic = new Topic(topicFilter);
        return ctrie.recursiveMatch(topic);
    }


    @Override
    public boolean subscribe(Subscription newSubscription) {
        ctrie.addToTree(newSubscription);
        return true;
    }


    @Override
    public boolean unSubscribe(String topicFilter, String clientID) {
        ctrie.removeFromTree(new Topic(topicFilter), clientID);
        return true;
    }

    @Override
    public int size() {
        return ctrie.size();
    }

    @Override
    public String dumpTree() {
        return ctrie.dumpTree();
    }
}


================================================
FILE: jmqtt-bus/src/main/java/org/jmqtt/bus/subscription/DefaultSubscriptionTreeMatcher.java
================================================
package org.jmqtt.bus.subscription;

import org.jmqtt.bus.subscription.model.Subscription;
import org.jmqtt.support.log.JmqttLogger;
import org.jmqtt.support.log.LogUtil;
import org.slf4j.Logger;

import java.util.HashSet;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.CopyOnWriteArraySet;

public class DefaultSubscriptionTreeMatcher implements SubscriptionMatcher {

    private static final Logger log = JmqttLogger.mqttLog;

    private final Object   lock   = new Object();
    private       TreeNode root   = new TreeNode(new Token("root"));
    private       Token    EMPTY  = new Token("");
    private       Token    SINGLE = new Token("+");
    private       Token    MULTY  = new Token("#");

    public DefaultSubscriptionTreeMatcher() {
    }

    @Override
    public boolean subscribe(Subscription subscription) {
        try {
            String topic = subscription.getTopic();
            TreeNode currentNode = recursionGetTreeNode(topic, root);
            Set<Subscription> subscriptions = currentNode.getSubscribers();
            if (subscriptions.contains(subscription)) {
                for (Subscription sub : subscriptions) {
                    if (sub.equals(subscription)) {
                        sub.setProperties(subscription.getProperties());
                        return true;
                    }
                }
            }
            currentNode.addSubscriber(subscription);
        } catch (Exception ex) {
            LogUtil.warn(log, "[Subscription] -> Subscribe failed,clientId={},topic={},qos={}",
                    subscription.getClientId(), subscription.getTopic(), subscription.getProperties());
            return true;
        }
        return true;
    }

    @Override
    public boolean unSubscribe(String topic, String clientId) {
        TreeNode currentNode = recursionGetTreeNode(topic, root);
        currentNode.getSubscribers().remove(new Subscription(clientId, topic, null));
        return true;
    }

    private TreeNode recursionGetTreeNode(String topic, TreeNode node) {
        String[] tokens = topic.split("/");
        Token token = new Token(tokens[0]);
        TreeNode matchNode = node.getChildNodeByToken(token);
        if (Objects.isNull(matchNode)) {
            synchronized (lock) {
                matchNode = node.getChildNodeByToken(token);
                if (Objects.isNull(matchNode)) {
                    matchNode = new TreeNode(token);
                    node.addChild(matchNode);
                }
            }
        }
        if (tokens.length > 1) {
            String childTopic = topic.substring(topic.indexOf("/") + 1);
            return recursionGetTreeNode(childTopic, matchNode);
        } else {
            return matchNode;
        }
    }

    @Override
    public Set<Subscription> match(String topic) {
        Set<Subscription> subscriptions = new HashSet<>();
        recursionMatch(topic, root, subscriptions);
        return subscriptions;
    }


    @Override
    public int size() {
        return 0;
    }

    @Override
    public String dumpTree() {
        return null;
    }

    private boolean innerIsMatch(String pubTopic, String subTopic) {
        if (pubTopic.equals(subTopic)) {
            return true;
        }
        String[] pubTokenStr = pubTopic.split("/");
        String[] subTokenStr = subTopic.split("/");
        int pubLen = pubTokenStr.length;
        int subLen = subTokenStr.length;
        if (pubLen != subLen) {
            Token lastSubToken = new Token(subTokenStr[subLen - 1]);
            if (subLen > pubLen || (!lastSubToken.equals(MULTY))) {
                return false;
            }
        }
        for (int i = 0; i < pubLen; i++) {
            Token pubToken = new Token(pubTokenStr[i]);
            Token subToken = new Token(subTokenStr[i]);
            if (subToken.equals(MULTY)) {
                return true;
            } else if ((!pubToken.equals(subToken)) && (!subToken.equals(SINGLE))) {
                return false;
            }
            if (i == subLen - 1) {
                return true;
            }
        }
        return false;
    }

    private void recursionMatch(String topic, TreeNode node,
                                Set<Subscription> subscriptions) {
        String[] topics = topic.split("/");
        Token token = new Token(topics[0]);
        List<TreeNode> childNodes = node.getChildren();
        if (topics.length > 1) {
            String nextTopic = topic.substring(topic.indexOf("/") + 1);
            for (TreeNode itemNode : childNodes) {
                if (itemNode.getToken().equals(token) || itemNode.getToken().equals(SINGLE)) {
                    recursionMatch(nextTopic, itemNode, subscriptions);
                }
                if (itemNode.getToken().equals(MULTY)) {
                    subscriptions.addAll(itemNode.getSubscribers());
                }
            }
        } else {
            for (TreeNode itemNode : childNodes) {
                if (itemNode.getToken().equals(token) || itemNode.getToken().equals(SINGLE)
                        || itemNode.getToken().equals(MULTY)) {
                    subscriptions.addAll(itemNode.getSubscribers());
                }
            }
        }
    }

    class Token {
        String token;

        public Token(String token) {
            this.token = token;
        }

        @Override
        public boolean equals(Object o) {
            if (this == o) {
                return true;
            }
            if (o == null || getClass() != o.getClass()) {
                return false;
            }
            Token token1 = (Token) o;
            return Objects.equals(token, token1.token);
        }

        @Override
        public int hashCode() {
            return Objects.hash(token);
        }
    }

    class TreeNode {

        private Token             token;
        private Set<Subscription> subscribers = new CopyOnWriteArraySet<>();
        private List<TreeNode>    children    = new CopyOnWriteArrayList<>();

        public TreeNode(Token token) {
            this.token = token;
        }

        public void addSubscriber(Subscription subscription) {
            this.subscribers.add(subscription);
        }

        public void addChild(TreeNode treeNode) {
            this.children.add(treeNode);
        }

        public Token getToken() {
            return token;
        }

        public Set<Subscription> getSubscribers() {
            return subscribers;
        }

        public List<TreeNode> getChildren() {
            return this.children;
        }

        ;

        public TreeNode getChildNodeByToken(Token token) {
            for (TreeNode childNode : this.children) {
                if (childNode.getToken().equals(token)) {
                    return childNode;
                }
            }
            return null;
        }
    }


}


================================================
FILE: jmqtt-bus/src/main/java/org/jmqtt/bus/subscription/DumpTreeVisitor.java
================================================
package org.jmqtt.bus.subscription;

import io.netty.util.internal.StringUtil;
import org.jmqtt.bus.subscription.model.Subscription;

class DumpTreeVisitor implements CTrie.IVisitor<String> {

    String s = "";

    @Override
    public void visit(CNode node, int deep) {
        String indentTabs = indentTabs(deep);
        s += indentTabs + (node.getToken() == null ? "''" : node.getToken().toString()) + prettySubscriptions(node) + "\n";
    }

    private String prettySubscriptions(CNode node) {
        if (node instanceof TNode) {
            return "TNode";
        }
        if (node.subscriptions.isEmpty()) {
            return StringUtil.EMPTY_STRING;
        }
        StringBuilder subScriptionsStr = new StringBuilder(" ~~[");
        int counter = 0;
        for (Subscription couple : node.subscriptions) {
            subScriptionsStr
                .append("{filter=").append(couple.getTopic()).append(", ")
                .append("properties=").append(couple.getProperties()).append(", ")
                .append("client='").append(couple.getClientId()).append("'}");
            counter++;
            if (counter < node.subscriptions.size()) {
                subScriptionsStr.append(";");
            }
        }
        return subScriptionsStr.append("]").toString();
    }

    private String indentTabs(int deep) {
        StringBuilder s = new StringBuilder();
        if (deep > 0) {
            s.append("    ");
            for (int i = 0; i < deep - 1; i++) {
                s.append("| ");
            }
            s.append("|-");
        }
        return s.toString();
    }

    @Override
    public String getResult() {
        return s;
    }
}


================================================
FILE: jmqtt-bus/src/main/java/org/jmqtt/bus/subscription/INode.java
================================================
package org.jmqtt.bus.subscription;

import java.util.concurrent.atomic.AtomicReference;

public class INode {
    private AtomicReference<CNode> mainNode = new AtomicReference<>();

    INode(CNode mainNode) {
        this.mainNode.set(mainNode);
        if (mainNode instanceof TNode) { // this should never happen
            throw new IllegalStateException("TNode should not be set on mainNnode");
        }
    }

    boolean compareAndSet(CNode old, CNode newNode) {
        return mainNode.compareAndSet(old, newNode);
    }

    boolean compareAndSet(CNode old, TNode newNode) {
        return mainNode.compareAndSet(old, newNode);
    }

    CNode mainNode() {
        return this.mainNode.get();
    }

    boolean isTombed() {
        return this.mainNode() instanceof TNode;
    }
}


================================================
FILE: jmqtt-bus/src/main/java/org/jmqtt/bus/subscription/SubscriptionCounterVisitor.java
================================================
package org.jmqtt.bus.subscription;

import java.util.concurrent.atomic.AtomicInteger;

class SubscriptionCounterVisitor implements CTrie.IVisitor<Integer> {

    private AtomicInteger accumulator = new AtomicInteger(0);

    @Override
    public void visit(CNode node, int deep) {
        accumulator.addAndGet(node.subscriptions.size());
    }

    @Override
    public Integer getResult() {
        return accumulator.get();
    }
}


================================================
FILE: jmqtt-bus/src/main/java/org/jmqtt/bus/subscription/SubscriptionMatcher.java
================================================
package org.jmqtt.bus.subscription;

import org.jmqtt.bus.subscription.model.Subscription;

import java.util.Set;

/**
 * Subscription tree
 * 订阅树处理
 *  TODO 1.支持共享订阅
 *  TODO 2.支持 p2p消息(jmqtt 特有)
 *  TODO 2.支持系统topic $SYS -> 需要定时采集本机节点等信息下发
 */
public interface SubscriptionMatcher {

    /**
     * add subscribe
     * @return  true:new subscribe,dispatcher retain message
     *           false:no need to dispatcher retain message
     */
    boolean subscribe(Subscription subscription);

    boolean unSubscribe(String topic,String clientId);

    /**
     * 获取匹配该topic下的非共享订阅者
     */
    Set<Subscription> match(String topic);


    int size();

    String dumpTree();

}


================================================
FILE: jmqtt-bus/src/main/java/org/jmqtt/bus/subscription/TNode.java
================================================
/*
 * Copyright (c) 2012-2018 The original author or authors
 * ------------------------------------------------------
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * and Apache License v2.0 which accompanies this distribution.
 *
 * The Eclipse Public License is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * The Apache License v2.0 is available at
 * http://www.opensource.org/licenses/apache2.0.php
 *
 * You may elect to redistribute this code under either of these licenses.
 */
package org.jmqtt.bus.subscription;

import org.jmqtt.bus.subscription.model.Subscription;

class TNode extends CNode {

    @Override
    public Token getToken() {
        throw new IllegalStateException("Can't be invoked on TNode");
    }

    @Override
    public void setToken(Token token) {
        throw new IllegalStateException("Can't be invoked on TNode");
    }

    @Override
    INode childOf(Token token) {
        throw new IllegalStateException("Can't be invoked on TNode");
    }

    @Override
    CNode copy() {
        throw new IllegalStateException("Can't be invoked on TNode");
    }

    @Override
    public void add(INode newINode) {
        throw new IllegalStateException("Can't be invoked on TNode");
    }

    @Override
    CNode addSubscription(Subscription newSubscription) {
        throw new IllegalStateException("Can't be invoked on TNode");
    }

    @Override
    boolean containsOnly(String clientId) {
        throw new IllegalStateException("Can't be invoked on TNode");
    }

    @Override
    public boolean contains(String clientId) {
        throw new IllegalStateException("Can't be invoked on TNode");
    }

    @Override
    void removeSubscriptionsFor(String clientId) {
        throw new IllegalStateException("Can't be invoked on TNode");
    }

    @Override
    boolean anyChildrenMatch(Token token) {
        return false;
    }
}


================================================
FILE: jmqtt-bus/src/main/java/org/jmqtt/bus/subscription/Token.java
================================================
package org.jmqtt.bus.subscription;


public class Token {

    public static final Token EMPTY = new Token("");
    public static final Token MULTI = new Token("#");
    public static final Token SINGLE = new Token("+");
    final String name;

    public Token(String s) {
        name = s;
    }

    protected String name() {
        return name;
    }

    protected boolean match(Token t) {
        if (MULTI.equals(t) || SINGLE.equals(t)) {
            return false;
        }

        if (MULTI.equals(this) || SINGLE.equals(this)) {
            return true;
        }

        return equals(t);
    }

    @Override
    public int hashCode() {
        int hash = 7;
        hash = 29 * hash + (this.name != null ? this.name.hashCode() : 0);
        return hash;
    }

    @Override
    public boolean equals(Object obj) {
        if (obj == null) {
            return false;
        }
        if (getClass() != obj.getClass()) {
            return false;
        }
        final Token other = (Token) obj;
        if ((this.name == null) ? (other.name != null) : !this.name.equals(other.name)) {
            return false;
        }
        return true;
    }

    @Override
    public String toString() {
        return name;
    }
}


================================================
FILE: jmqtt-bus/src/main/java/org/jmqtt/bus/subscription/Topic.java
================================================
package org.jmqtt.bus.subscription;

import org.jmqtt.support.log.JmqttLogger;
import org.jmqtt.support.log.LogUtil;
import org.slf4j.Logger;

import java.io.Serializable;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;

public class Topic implements Serializable , Comparable<Topic> {

    private static final Logger log = JmqttLogger.busLog;

    private static final long serialVersionUID = 2438799283749822L;

    private final String topic;

    private transient List<Token> tokens;

    private transient boolean valid;

    /**
     * Factory method
     *
     * @param s the topic string (es "/a/b").
     * @return the created Topic instance.
     * */
    public static Topic asTopic(String s) {
        return new Topic(s);
    }

    public Topic(String topic) {
        this.topic = topic;
    }

    Topic(List<Token> tokens) {
        this.tokens = tokens;
        List<String> strTokens = tokens.stream().map(Token::toString).collect(Collectors.toList());
        this.topic = String.join("/", strTokens);
        this.valid = true;
    }

    public List<Token> getTokens() {
        if (tokens == null) {
            try {
                tokens = parseTopic(topic);
                valid = true;
            } catch (ParseException e) {
                valid = false;
                LogUtil.error(log,"Error parsing the topic: {}, message: {}", topic, e.getMessage());
            }
        }

        return tokens;
    }

    private List<Token> parseTopic(String topic) throws ParseException {
        if (topic.length() == 0) {
            throw new ParseException("Bad format of topic, topic MUST be at least 1 character [MQTT-4.7.3-1] and " +
                    "this was empty", 0);
        }
        List<Token> res = new ArrayList<>();
        String[] splitted = topic.split("/");

        if (splitted.length == 0) {
            res.add(Token.EMPTY);
        }

        if (topic.endsWith("/")) {
            // Add a fictious space
            String[] newSplitted = new String[splitted.length + 1];
            System.arraycopy(splitted, 0, newSplitted, 0, splitted.length);
            newSplitted[splitted.length] = "";
            splitted = newSplitted;
        }

        for (int i = 0; i < splitted.length; i++) {
            String s = splitted[i];
            if (s.isEmpty()) {
                // if (i != 0) {
                // throw new ParseException("Bad format of topic, expetec topic name between
                // separators", i);
                // }
                res.add(Token.EMPTY);
            } else if (s.equals("#")) {
                // check that multi is the last symbol
                if (i != splitted.length - 1) {
                    throw new ParseException(
                            "Bad format of topic, the multi symbol (#) has to be the last one after a separator",
                            i);
                }
                res.add(Token.MULTI);
            } else if (s.contains("#")) {
                throw new ParseException("Bad format of topic, invalid subtopic name: " + s, i);
            } else if (s.equals("+")) {
                res.add(Token.SINGLE);
            } else if (s.contains("+")) {
                throw new ParseException("Bad format of topic, invalid subtopic name: " + s, i);
            } else {
                res.add(new Token(s));
            }
        }

        return res;
    }

    public Token headToken() {
        final List<Token> tokens = getTokens();
        if (tokens.isEmpty()) {
            return null;
        }
        return tokens.get(0);
    }

    public boolean isEmpty() {
        final List<Token> tokens = getTokens();
        return tokens == null || tokens.isEmpty();
    }

    public Topic exceptHeadToken() {
        List<Token> tokens = getTokens();
        if (tokens.isEmpty()) {
            return new Topic(Collections.emptyList());
        }
        List<Token> tokensCopy = new ArrayList<>(tokens);
        tokensCopy.remove(0);
        return new Topic(tokensCopy);
    }

    public boolean isValid() {
        if (tokens == null)
            getTokens();

        return valid;
    }

    public boolean match(Topic subscriptionTopic) {
        List<Token> msgTokens = getTokens();
        List<Token> subscriptionTokens = subscriptionTopic.getTokens();
        int i = 0;
        for (; i < subscriptionTokens.size(); i++) {
            Token subToken = subscriptionTokens.get(i);
            if (!Token.MULTI.equals(subToken) && !Token.SINGLE.equals(subToken)) {
                if (i >= msgTokens.size()) {
                    return false;
                }
                Token msgToken = msgTokens.get(i);
                if (!msgToken.equals(subToken)) {
                    return false;
                }
            } else {
                if (Token.MULTI.equals(subToken)) {
                    return true;
                }
            }
        }
        return i == msgTokens.size();
    }

    @Override
    public String toString() {
        return topic;
    }

    @Override
    public boolean equals(Object obj) {
        if (obj == null) {
            return false;
        }
        if (getClass() != obj.getClass()) {
            return false;
        }
        Topic other = (Topic) obj;

        return Objects.equals(this.topic, other.topic);
    }

    @Override
    public int hashCode() {
        return topic.hashCode();
    }

    @Override
    public int compareTo(Topic o) {
        return topic.compareTo(o.topic);
    }
}


================================================
FILE: jmqtt-bus/src/main/java/org/jmqtt/bus/subscription/model/Subscription.java
================================================
package org.jmqtt.bus.subscription.model;

import java.util.Map;
import java.util.Objects;

/**
 * 订阅关系
 */
public class Subscription {
    private String             clientId;
    private String             topic;
    private Map<String,Object> properties;

    public Subscription(String clientId,String topic){
        this.clientId = clientId;
        this.topic = topic;
    }

    public Subscription(String clientId,String topic,Map<String,Object> properties){
        this.clientId = clientId;
        this.topic = topic;
        this.properties = properties;
    }

    public Subscription(Subscription origin){
        this.clientId = origin.getClientId();
        this.topic = origin.getTopic();
        this.properties = origin.getProperties();
    }

    public <T> T getProperty(String key){
        if (properties == null) {
            return null;
        }
        return (T) properties.get(key);
    }


    public String getClientId() {
        return clientId;
    }

    public void setClientId(String clientId) {
        this.clientId = clientId;
    }

    public String getTopic() {
        return topic;
    }

    public void setTopic(String topic) {
        this.topic = topic;
    }

    public Map<String, Object> getProperties() {
        return properties;
    }

    public void setProperties(Map<String, Object> properties) {
        this.properties = properties;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Subscription that = (Subscription) o;
        return Objects.equals(clientId, that.clientId) &&
                Objects.equals(topic, that.topic);
    }

    @Override
    public int hashCode() {
        return Objects.hash(clientId, topic);
    }

    @Override
    public String toString() {
        return "Subscription{" +
                "clientId='" + clientId + '\'' +
                ", topic='" + topic + '\'' +
                ", properties=" + properties +
                '}';
    }
}


================================================
FILE: jmqtt-bus/src/test/java/org/jmqtt/AppTest.java
================================================
package org.jmqtt;

import org.junit.jupiter.api.Test;

import static org.junit.jupiter.api.Assertions.assertTrue;

/**
 * Unit test for simple App.
 */
public class AppTest
{
    /**
     * Rigorous Test :-)
     */
    @Test
    public void shouldAnswerWithTrue()
    {
        assertTrue( true );
    }
}


================================================
FILE: jmqtt-bus/src/test/java/org/jmqtt/bus/subscription/CTrieSubscriptionMatcherMatchingTest.java
================================================
/*
 * Copyright (c) 2012-2018 The original author or authors
 * ------------------------------------------------------
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * and Apache License v2.0 which accompanies this distribution.
 *
 * The Eclipse Public License is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * The Apache License v2.0 is available at
 * http://www.opensource.org/licenses/apache2.0.php
 *
 * You may elect to redistribute this code under either of these licenses.
 */
package org.jmqtt.bus.subscription;

import org.jmqtt.bus.subscription.model.Subscription;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;

import java.util.Optional;
import java.util.Set;

import static org.assertj.core.api.Assertions.assertThat;
import static org.jmqtt.bus.subscription.CTrieTest.clientSubOnTopic;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;

class CTrieSubscriptionMatcherMatchingTest {

    private CTrieSubscriptionMatcher sut;

    @BeforeEach
    public void setUp() {
        sut = new CTrieSubscriptionMatcher();
    }

    @Test
    public void testMatchSimple() {
        Subscription slashSub = clientSubOnTopic("TempSensor1", "/");
        sut.subscribe(slashSub);
        assertThat(sut.match("finance")).isEmpty();

        Subscription slashFinanceSub = clientSubOnTopic("TempSensor1", "/finance");
        sut.subscribe(slashFinanceSub);
        assertThat(sut.match("finance")).isEmpty();

        assertThat(sut.match("/finance")).contains(slashFinanceSub);
        assertThat(sut.match("/")).contains(slashSub);
    }

    @Test
    public void testMatchSimpleMulti() {
        Subscription anySub = clientSubOnTopic("TempSensor1", "#");
        sut.subscribe(anySub);
        assertThat(sut.match("finance")).contains(anySub);

        Subscription financeAnySub = clientSubOnTopic("TempSensor1", "finance/#");
        sut.subscribe(financeAnySub);
        assertThat(sut.match("finance")).containsExactlyInAnyOrder(financeAnySub, anySub);
    }

    @Test
    public void testMatchingDeepMulti_one_layer() {
        Subscription anySub = clientSubOnTopic("AllSensor1", "#");
        Subscription financeAnySub = clientSubOnTopic("FinanceSensor", "finance/#");
        sut.subscribe(anySub);
        sut.subscribe(financeAnySub);

        // Verify
        assertThat(sut.match("finance/stock"))
            .containsExactlyInAnyOrder(financeAnySub, anySub);
        assertThat(sut.match("finance/stock/ibm"))
            .containsExactlyInAnyOrder(financeAnySub, anySub);
//        System.out.println(sut.dumpTree());
    }

    @Test
    public void testMatchingDeepMulti_two_layer() {
        Subscription financeAnySub = clientSubOnTopic("FinanceSensor", "finance/stock/#");
        sut.subscribe(financeAnySub);

        // Verify
        assertThat(sut.match("finance/stock/ibm")).containsExactly(financeAnySub);
    }

    @Test
    public void testMatchSimpleSingle() {
        Subscription anySub = clientSubOnTopic("AnySensor", "+");
        sut.subscribe(anySub);
        assertThat(sut.match("finance")).containsExactly(anySub);

        Subscription financeOne = clientSubOnTopic("AnySensor", "finance/+");
        sut.subscribe(financeOne);
        assertThat(sut.match("finance/stock")).containsExactly(financeOne);
    }

    @Test
    public void testMatchManySingle() {
        Subscription manySub = clientSubOnTopic("AnySensor", "+/+");
        sut.subscribe(manySub);

        // verify
        assertThat(sut.match("/finance")).contains(manySub);
    }

    @Test
    public void testMatchSlashSingle() {
        Subscription slashPlusSub = clientSubOnTopic("AnySensor", "/+");
        sut.subscribe(slashPlusSub);
        Subscription anySub = clientSubOnTopic("AnySensor", "+");
        sut.subscribe(anySub);

        // Verify
        assertThat(sut.match("/finance")).containsOnly(slashPlusSub);
        assertThat(sut.match("/finance")).doesNotContain(anySub);
    }

    @Test
    public void testMatchManyDeepSingle() {
        Subscription slashPlusSub = clientSubOnTopic("FinanceSensor1", "/finance/+/ibm");
        sut.subscribe(slashPlusSub);
        Subscription slashPlusDeepSub = clientSubOnTopic("FinanceSensor2", "/+/stock/+");
        sut.subscribe(slashPlusDeepSub);

        // Verify
        assertThat(sut.match("/finance/stock/ibm"))
            .containsExactlyInAnyOrder(slashPlusSub, slashPlusDeepSub);
    }

    @Test
    public void testMatchSimpleMulti_allTheTree() {
        Subscription sub = clientSubOnTopic("AnySensor1", "#");
        sut.subscribe(sub);

        assertThat(sut.match("finance")).isNotEmpty();
        assertThat(sut.match("finance/ibm")).isNotEmpty();
    }

    @Test
    public void rogerLightTopicMatches() {
        assertMatch("foo/bar", "foo/bar");
        assertMatch("foo/bar", "foo/bar");
        assertMatch("foo/+", "foo/bar");
        assertMatch("foo/+/baz", "foo/bar/baz");
        assertMatch("foo/+/#", "foo/bar/baz");
        assertMatch("#", "foo/bar/baz");

        assertNotMatch("foo/bar", "foo");
        assertNotMatch("foo/+", "foo/bar/baz");
        assertNotMatch("foo/+/baz", "foo/bar/bar");
        assertNotMatch("foo/+/#", "fo2/bar/baz");

        assertMatch("#", "/foo/bar");
        assertMatch("/#", "/foo/bar");
        assertNotMatch("/#", "foo/bar");

        assertMatch("foo//bar", "foo//bar");
        assertMatch("foo//+", "foo//bar");
        assertMatch("foo/+/+/baz", "foo///baz");
        assertMatch("foo/bar/+", "foo/bar/");
    }

    private void assertMatch(String s, String t) {
        sut = new CTrieSubscriptionMatcher();

        Subscription sub = clientSubOnTopic("AnySensor1", s);
        sut.subscribe(sub);

        assertThat(sut.match(t)).isNotEmpty();
    }

    private void assertNotMatch(String subscription, String topic) {
        sut = new CTrieSubscriptionMatcher();

        Subscription sub = clientSubOnTopic("AnySensor1", subscription);
        sut.subscribe(sub);

        assertThat(sut.match(topic)).isEmpty();
    }

    @Test
    public void testOverlappingSubscriptions() {
        Subscription genericSub = new Subscription("Sensor1", "a/+", null);
        sut.subscribe(genericSub);

        Subscription specificSub = new Subscription("Sensor1", "a/b", null);
        sut.subscribe(specificSub);

        //Exercise
        final Set<Subscription> matchingForSpecific = sut.match("a/b");

        // Verify
        assertThat(matchingForSpecific.size()).isEqualTo(1);
    }

    @Test
    public void removeSubscription_withDifferentClients_subscribedSameTopic() {
        Subscription slashSub = clientSubOnTopic("Sensor1", "/topic");
        sut.subscribe(slashSub);
        Subscription slashSub2 = clientSubOnTopic("Sensor2", "/topic");
        sut.subscribe(slashSub2);

        // Exercise
        sut.unSubscribe("/topic", slashSub2.getClientId());

        // Verify
        Subscription remainedSubscription = sut.match("/topic").iterator().next();
        assertThat(remainedSubscription.getClientId()).isEqualTo(slashSub.getClientId());
        assertEquals(slashSub.getClientId(), remainedSubscription.getClientId());
    }

    @Test
    public void removeSubscription_sameClients_subscribedSameTopic() {
        Subscription slashSub = clientSubOnTopic("Sensor1", "/topic");
        sut.subscribe(slashSub);

        // Exercise
        sut.unSubscribe("/topic", slashSub.getClientId());

        // Verify
        final Set<Subscription> matchingSubscriptions = sut.match("/topic");
        assertThat(matchingSubscriptions).isEmpty();
    }

    /*
     * Test for Issue #49
     */
    @Test
    public void duplicatedSubscriptionsWithDifferentQos() {
        Subscription client2Sub = new Subscription("client2", "client/test/b", null);
        this.sut.subscribe(client2Sub);
        Subscription client1SubQoS0 = new Subscription("client1", "client/test/b", null);
        this.sut.subscribe(client1SubQoS0);

        Subscription client1SubQoS2 = new Subscription("client1", "client/test/b", null);
        this.sut.subscribe(client1SubQoS2);

        // Verify
        Set<Subscription> subscriptions = this.sut.match("client/test/b");
        assertThat(subscriptions).contains(client1SubQoS2);
        assertThat(subscriptions).contains(client2Sub);

        final Optional<Subscription> matchingClient1Sub = subscriptions
            .stream()
            .filter(s -> s.equals(client1SubQoS0))
            .findFirst();
        assertTrue(matchingClient1Sub.isPresent());
        Subscription client1Sub = matchingClient1Sub.get();
    }
}


================================================
FILE: jmqtt-bus/src/test/java/org/jmqtt/bus/subscription/CTrieTest.java
================================================
/*
 * Copyright (c) 2012-2018 The original author or authors
 * ------------------------------------------------------
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * and Apache License v2.0 which accompanies this distribution.
 *
 * The Eclipse Public License is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * The Apache License v2.0 is available at
 * http://www.opensource.org/licenses/apache2.0.php
 *
 * You may elect to redistribute this code under either of these licenses.
 */
package org.jmqtt.bus.subscription;

import org.jmqtt.bus.subscription.model.Subscription;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;

import java.util.Optional;
import java.util.Set;

import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.api.Assertions.*;

public class CTrieTest {

    private CTrie sut;

    @BeforeEach
    public void setUp() {
        sut = new CTrie();
    }

    @Test
    public void testAddOnSecondLayerWithEmptyTokenOnEmptyTree() {
        //Exercise
        sut.addToTree(clientSubOnTopic("TempSensor1", "/"));

        //Verify
        final Optional<CNode> matchedNode = sut.lookup(new Topic("/"));
        assertTrue(matchedNode.isPresent(), "Node on path / must be present");
        //verify structure, only root INode and the first CNode should be present
        assertThat(this.sut.root.mainNode().subscriptions).isEmpty();
        assertThat(this.sut.root.mainNode().allChildren()).isNotEmpty();

        INode firstLayer = this.sut.root.mainNode().allChildren().get(0);
        assertThat(firstLayer.mainNode().subscriptions).isEmpty();
        assertThat(firstLayer.mainNode().allChildren()).isNotEmpty();

        INode secondLayer = firstLayer.mainNode().allChildren().get(0);
        assertThat(secondLayer.mainNode().subscriptions).isNotEmpty();
        assertThat(secondLayer.mainNode().allChildren()).isEmpty();
    }

    @Test
    public void testAddFirstLayerNodeOnEmptyTree() {
        //Exercise
        sut.addToTree(clientSubOnTopic("TempSensor1", "/temp"));

        //Verify
        final Optional<CNode> matchedNode = sut.lookup(new Topic("/temp"));
        assertTrue(matchedNode.isPresent(), "Node on path /temp must be present");
        assertFalse(matchedNode.get().subscriptions.isEmpty());
    }

    @Test
    public void testLookup() {
        final Subscription existingSubscription = clientSubOnTopic("TempSensor1", "/temp");
        sut.addToTree(existingSubscription);

        //Exercise
        final Optional<CNode> matchedNode = sut.lookup(new Topic("/humidity"));

        //Verify
        assertFalse(matchedNode.isPresent(), "Node on path /humidity can't be present");
    }

    @Test
    public void testAddNewSubscriptionOnExistingNode() {
        final Subscription existingSubscription = clientSubOnTopic("TempSensor1", "/temp");
        sut.addToTree(existingSubscription);

        //Exercise
        final Subscription newSubscription = clientSubOnTopic("TempSensor2", "/temp");
        sut.addToTree(newSubscription);

        //Verify
        final Optional<CNode> matchedNode = sut.lookup(new Topic("/temp"));
        assertTrue(matchedNode.isPresent(), "Node on path /temp must be present");
        final Set<Subscription> subscriptions = matchedNode.get().subscriptions;
        assertTrue(subscriptions.contains(newSubscription));
    }

    @Test
    public void testAddNewDeepNodes() {
        sut.addToTree(clientSubOnTopic("TempSensorRM", "/italy/roma/temp"));
        sut.addToTree(clientSubOnTopic("TempSensorFI", "/italy/firenze/temp"));
        sut.addToTree(clientSubOnTopic("HumSensorFI", "/italy/roma/humidity"));
        final Subscription happinessSensor = clientSubOnTopic("HappinessSensor", "/italy/happiness");
        sut.addToTree(happinessSensor);

        //Verify
        final Optional<CNode> matchedNode = sut.lookup(new Topic("/italy/happiness"));
        assertTrue(matchedNode.isPresent(), "Node on path /italy/happiness must be present");
        final Set<Subscription> subscriptions = matchedNode.get().subscriptions;
        assertTrue(subscriptions.contains(happinessSensor));
    }

    static Subscription clientSubOnTopic(String clientID, String topicName) {
        return new Subscription(clientID, topicName, null);
    }

    @Test
    public void givenTreeWithSomeNodeWhenRemoveContainedSubscriptionThenNodeIsUpdated() {
        sut.addToTree(clientSubOnTopic("TempSensor1", "/temp"));

        //Exercise
        sut.removeFromTree(new Topic("/temp"), "TempSensor1");

        //Verify
        final Optional<CNode> matchedNode = sut.lookup(new Topic("/temp"));
        assertFalse(matchedNode.isPresent(), "Node on path /temp can't be present");
    }

    @Test
    public void givenTreeWithSomeNodeUnsubscribeAndResubscribeCleanTomb() {
        sut.addToTree(clientSubOnTopic("TempSensor1", "test"));
        sut.removeFromTree(new Topic("test"), "TempSensor1");

        sut.addToTree(clientSubOnTopic("TempSensor1", "test"));
        assertEquals(1, sut.root.mainNode().allChildren().size());  // looking to see if TNode is cleaned up
    }

    @Test
    public void givenTreeWithSomeNodeWhenRemoveMultipleTimes() {
        sut.addToTree(clientSubOnTopic("TempSensor1", "test"));

        // make sure no TNode exceptions
        sut.removeFromTree(new Topic("test"), "TempSensor1");
        sut.removeFromTree(new Topic("test"), "TempSensor1");
        sut.removeFromTree(new Topic("test"), "TempSensor1");
        sut.removeFromTree(new Topic("test"), "TempSensor1");

        //Verify
        final Optional<CNode> matchedNode = sut.lookup(new Topic("/temp"));
        assertFalse(matchedNode.isPresent(), "Node on path /temp can't be present");
    }

    @Test
    public void givenTreeWithSomeDeepNodeWhenRemoveMultipleTimes() {
        sut.addToTree(clientSubOnTopic("TempSensor1", "/test/me/1/2/3"));

        // make sure no TNode exceptions
        sut.removeFromTree(new Topic("/test/me/1/2/3"), "TempSensor1");
        sut.removeFromTree(new Topic("/test/me/1/2/3"), "TempSensor1");
        sut.removeFromTree(new Topic("/test/me/1/2/3"), "TempSensor1");

        //Verify
        final Optional<CNode> matchedNode = sut.lookup(new Topic("/temp"));
        assertFalse(matchedNode.isPresent(), "Node on path /temp can't be present");
    }

    @Test
    public void givenTreeWithSomeNodeHierarchyWhenRemoveContainedSubscriptionThenNodeIsUpdated() {
        sut.addToTree(clientSubOnTopic("TempSensor1", "/temp/1"));
        sut.addToTree(clientSubOnTopic("TempSensor1", "/temp/2"));

        //Exercise
        sut.removeFromTree(new Topic("/temp/1"), "TempSensor1");

        sut.removeFromTree(new Topic("/temp/1"), "TempSensor1");
        final Set<Subscription> matchingSubs = sut.recursiveMatch(new Topic("/temp/2"));

        //Verify
        final Subscription expectedMatchingsub = new Subscription("TempSensor1", "/temp/2");
        assertThat(matchingSubs).contains(expectedMatchingsub);
    }

    @Test
    public void givenTreeWithSomeNodeHierarchWhenRemoveContainedSubscriptionSmallerThenNodeIsNotUpdated() {
        sut.addToTree(clientSubOnTopic("TempSensor1", "/temp/1"));
        sut.addToTree(clientSubOnTopic("TempSensor1", "/temp/2"));

        //Exercise
        sut.removeFromTree(new Topic("/temp"), "TempSensor1");

        final Set<Subscription> matchingSubs1 = sut.recursiveMatch(new Topic("/temp/1"));
        final Set<Subscription> matchingSubs2 = sut.recursiveMatch(new Topic("/temp/2"));

        //Verify
        // not clear to me, but I believe /temp unsubscribe should not unsub you from downstream /temp/1 or /temp/2
        final Subscription expectedMatchingsub1 = new Subscription("TempSensor1", "/temp/1");
        assertThat(matchingSubs1).contains(expectedMatchingsub1);
        final Subscription expectedMatchingsub2 = new Subscription("TempSensor1", "/temp/2");
        assertThat(matchingSubs2).contains(expectedMatchingsub2);
    }

    @Test
    public void givenTreeWithDeepNodeWhenRemoveContainedSubscriptionThenNodeIsUpdated() {
        sut.addToTree(clientSubOnTopic("TempSensor1", "/bah/bin/bash"));

        sut.removeFromTree(new Topic("/bah/bin/bash"), "TempSensor1");

        //Verify
        final Optional<CNode> matchedNode = sut.lookup(new Topic("/bah/bin/bash"));
        assertFalse(matchedNode.isPresent(), "Node on path /temp can't be present");
    }

    @Test
    public void testMatchSubscriptionNoWildcards() {
        sut.addToTree(clientSubOnTopic("TempSensor1", "/temp"));

        //Exercise
        final Set<Subscription> matchingSubs = sut.recursiveMatch(new Topic("/temp"));

        //Verify
        final Subscription expectedMatchingsub = new Subscription("TempSensor1", "/temp", null);
        assertThat(matchingSubs).contains(expectedMatchingsub);
    }

    @Test
    public void testRemovalInnerTopicOffRootSameClient() {
        sut.addToTree(clientSubOnTopic("TempSensor1", "temp"));
        sut.addToTree(clientSubOnTopic("TempSensor1", "temp/1"));

        //Exercise
        final Set<Subscription> matchingSubs1 = sut.recursiveMatch(new Topic("temp"));
        final Set<Subscription> matchingSubs2 = sut.recursiveMatch(new Topic("temp/1"));

        //Verify
        final Subscription expectedMatchingsub1 = new Subscription("TempSensor1", "temp", null);
        final Subscription expectedMatchingsub2 = new Subscription("TempSensor1", "temp/1", null);

        assertThat(matchingSubs1).contains(expectedMatchingsub1);
        assertThat(matchingSubs2).contains(expectedMatchingsub2);

        sut.removeFromTree(new Topic("temp"), "TempSensor1");

        //Exercise
        final Set<Subscription> matchingSubs3 = sut.recursiveMatch(new Topic("temp"));
        final Set<Subscription> matchingSubs4 = sut.recursiveMatch(new Topic("temp/1"));

        assertThat(matchingSubs3).doesNotContain(expectedMatchingsub1);
        assertThat(matchingSubs4).contains(expectedMatchingsub2);
    }

    @Test
    public void testRemovalInnerTopicOffRootDiffClient() {
        sut.addToTree(clientSubOnTopic("TempSensor1", "temp"));
        sut.addToTree(clientSubOnTopic("TempSensor2", "temp/1"));

        //Exercise
        final Set<Subscription> matchingSubs1 = sut.recursiveMatch(new Topic("temp"));
        final Set<Subscription> matchingSubs2 = sut.recursiveMatch(new Topic("temp/1"));

        //Verify
        final Subscription expectedMatchingsub1 = new Subscription("TempSensor1", "temp", null);
        final Subscription expectedMatchingsub2 = new Subscription("TempSensor2", "temp/1", null);

        assertThat(matchingSubs1).contains(expectedMatchingsub1);
        assertThat(matchingSubs2).contains(expectedMatchingsub2);

        sut.removeFromTree(new Topic("temp"), "TempSensor1");

        //Exercise
        final Set<Subscription> matchingSubs3 = sut.recursiveMatch(new Topic("temp"));
        final Set<Subscription> matchingSubs4 = sut.recursiveMatch(new Topic("temp/1"));

        assertThat(matchingSubs3).doesNotContain(expectedMatchingsub1);
        assertThat(matchingSubs4).contains(expectedMatchingsub2);
    }

    @Test
    public void testRemovalOuterTopicOffRootDiffClient() {
        sut.addToTree(clientSubOnTopic("TempSensor1", "temp"));
        sut.addToTree(clientSubOnTopic("TempSensor2", "temp/1"));

        //Exercise
        final Set<Subscription> matchingSubs1 = sut.recursiveMatch(new Topic("temp"));
        final Set<Subscription> matchingSubs2 = sut.recursiveMatch(new Topic("temp/1"));

        //Verify
        final Subscription expectedMatchingsub1 = new Subscription("TempSensor1", "temp", null);
        final Subscription expectedMatchingsub2 = new Subscription("TempSensor2", "temp/1", null);

        assertThat(matchingSubs1).contains(expectedMatchingsub1);
        assertThat(matchingSubs2).contains(expectedMatchingsub2);

        sut.removeFromTree(new Topic("temp/1"), "TempSensor2");

        //Exercise
        final Set<Subscription> matchingSubs3 = sut.recursiveMatch(new Topic("temp"));
        final Set<Subscription> matchingSubs4 = sut.recursiveMatch(new Topic("temp/1"));

        assertThat(matchingSubs3).contains(expectedMatchingsub1);
        assertThat(matchingSubs4).doesNotContain(expectedMatchingsub2);
    }
}


================================================
FILE: jmqtt-bus/src/test/java/org/jmqtt/bus/subscription/TopicTest.java
================================================
/*
 * Copyright (c) 2012-2018 The original author or authors
 * ------------------------------------------------------
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * and Apache License v2.0 which accompanies this distribution.
 *
 * The Eclipse Public License is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * The Apache License v2.0 is available at
 * http://www.opensource.org/licenses/apache2.0.php
 *
 * You may elect to redistribute this code under either of these licenses.
 */
package org.jmqtt.bus.subscription;

import org.assertj.core.api.AbstractAssert;
import org.assertj.core.api.Assertions;
import org.junit.jupiter.api.Test;

import static org.junit.jupiter.api.Assertions.assertEquals;

public class TopicTest {

    @Test
    public void testParseTopic() {
        assertThat(new Topic("finance/stock/ibm")).containsToken("finance", "stock", "ibm");

        assertThat(new Topic("/finance/stock/ibm")).containsToken(Token.EMPTY, "finance", "stock", "ibm");

        assertThat(new Topic("/")).containsToken(Token.EMPTY, Token.EMPTY);
    }

    @Test
    public void testParseTopicMultiValid() {
        assertThat(new Topic("finance/stock/#")).containsToken("finance", "stock", Token.MULTI);

        assertThat(new Topic("#")).containsToken(Token.MULTI);
    }

    @Test
    public void testValidationProcess() {
        // TopicMultiInTheMiddle
        assertThat(new Topic("finance/#/closingprice")).isInValid();

        // MultiNotAfterSeparator
        assertThat(new Topic("finance#")).isInValid();

        // TopicMultiNotAlone
        assertThat(new Topic("/finance/#closingprice")).isInValid();

        // SingleNotAferSeparator
        assertThat(new Topic("finance+")).isInValid();

        assertThat(new Topic("finance/+")).isValid();
    }

    @Test
    public void testParseTopicSingleValid() {
        assertThat(new Topic("finance/stock/+")).containsToken("finance", "stock", Token.SINGLE);

        assertThat(new Topic("+")).containsToken(Token.SINGLE);

        assertThat(new Topic("finance/+/ibm")).containsToken("finance", Token.SINGLE, "ibm");
    }

    @Test
    public void testMatchTopics_simple() {
        assertThat(new Topic("/")).matches("/");
        assertThat(new Topic("/finance")).matches("/finance");
    }

    @Test
    public void testMatchTopics_multi() {
        assertThat(new Topic("finance")).matches("#");
        assertThat(new Topic("finance")).matches("finance/#");
        assertThat(new Topic("finance/stock")).matches("finance/#");
        assertThat(new Topic("finance/stock/ibm")).matches("finance/#");
    }

    @Test
    public void testMatchTopics_single() {
        assertThat(new Topic("finance")).matches("+");
        assertThat(new Topic("finance/stock")).matches("finance/+");
        assertThat(new Topic("finance")).doesNotMatch("finance/+");
        assertThat(new Topic("/finance")).matches("/+");
        assertThat(new Topic("/finance")).doesNotMatch("+");
        assertThat(new Topic("/finance")).matches("+/+");
        assertThat(new Topic("/finance/stock/ibm")).matches("/finance/+/ibm");
        assertThat(new Topic("/")).matches("+/+");
        assertThat(new Topic("sport/")).matches("sport/+");
        assertThat(new Topic("/finance/stock")).doesNotMatch("+");
    }

    @Test
    public void rogerLightMatchTopics() {
        assertThat(new Topic("foo/bar")).matches("foo/bar");
        assertThat(new Topic("foo/bar")).matches("foo/+");
        assertThat(new Topic("foo/bar/baz")).matches("foo/+/baz");
        assertThat(new Topic("foo/bar/baz")).matches("foo/+/#");
        assertThat(new Topic("foo/bar/baz")).matches("#");

        assertThat(new Topic("foo")).doesNotMatch("foo/bar");
        assertThat(new Topic("foo/bar/baz")).doesNotMatch("foo/+");
        assertThat(new Topic("foo/bar/bar")).doesNotMatch("foo/+/baz");
        assertThat(new Topic("fo2/bar/baz")).doesNotMatch("foo/+/#");

        assertThat(new Topic("/foo/bar")).matches("#");
        assertThat(new Topic("/foo/bar")).matches("/#");
        assertThat(new Topic("foo/bar")).doesNotMatch("/#");

        assertThat(new Topic("foo//bar")).matches("foo//bar");
        assertThat(new Topic("foo//bar")).matches("foo//+");
        assertThat(new Topic("foo///baz")).matches("foo/+/+/baz");
        assertThat(new Topic("foo/bar/")).matches("foo/bar/+");
    }

    @Test
    public void exceptHeadToken() {
        assertEquals(Topic.asTopic("token"), Topic.asTopic("/token").exceptHeadToken());
        assertEquals(Topic.asTopic("a/b"), Topic.asTopic("/a/b").exceptHeadToken());
    }

    public static TopicAssert assertThat(Topic topic) {
        return new TopicAssert(topic);
    }

    static class TopicAssert extends AbstractAssert<TopicAssert, Topic> {

        TopicAssert(Topic actual) {
            super(actual, TopicAssert.class);
        }

        public TopicAssert matches(String topic) {
            Assertions.assertThat(actual.match(new Topic(topic))).isTrue();

            return myself;
        }

        public TopicAssert doesNotMatch(String topic) {
            Assertions.assertThat(actual.match(new Topic(topic))).isFalse();

            return myself;
        }

        public TopicAssert containsToken(Object... tokens) {
            Assertions.assertThat(actual.getTokens()).containsExactly(asArray(tokens));

            return myself;
        }

        private Token[] asArray(Object... l) {
            Token[] tokens = new Token[l.length];
            for (int i = 0; i < l.length; i++) {
                Object o = l[i];
                if (o instanceof Token) {
                    tokens[i] = (Token) o;
                } else {
                    tokens[i] = new Token(o.toString());
                }
            }

            return tokens;
        }

        public TopicAssert isValid() {
            Assertions.assertThat(actual.isValid()).isTrue();

            return myself;
        }

        public TopicAssert isInValid() {
            Assertions.assertThat(actual.isValid()).isFalse();

            return myself;
        }
    }
}


================================================
FILE: jmqtt-doc/README.md
================================================
官网搭建指导文件

1. clone本项目
2. 安装npm或yarn,具体参考:https://www.vuepress.cn/guide/getting-started.html
3. 执行:yarn add -D vuepress
4. 本地启动:yarn dev
5. 构建:yarn build




================================================
FILE: jmqtt-doc/docs/README.md
================================================
## 快速上手

### 安装主题

1. 下载 [release](https://github.com/Cicizz/jmqtt/releases)(3.x以上版本) 或`clone`本项目:
2. 在jmqtt根目录执行::
```bash
mvn -Ppackage-all -DskipTests clean install -U
```
3. 配置相应的配置文件,初始化db的sql文件:`/jmqtt-broker/resources/conf`目录下
4. 执行启动命令:`java -jar jmqtt-broker-3.0.0.jar -h ${conf文件目录}` -h后是配置文件目录,里面需要包含jmqtt.properties和log4j2.xml等配置文件

### 测试
下载客户端:[mqtt客户端](https://mqttx.app/cn/)
或 直接使用websocket测试:`/jmqtt/jmqtt-examples`




<p>&nbsp; </p>  

[我也想为贡献者之一?](https://github.com/Cicizz/jmqtt/pulls)

<p>&nbsp; </p> 


<Msg />


================================================
FILE: jmqtt-doc/docs/TEST_REPORT.md
================================================
# Jmqtt 最新版功能及性能测试报告
线上版连接:https://www.yuque.com/tristan-ku8np/zze/xghq80
最新版链接:[https://github.com/Cicizz/jmqtt](https://github.com/Cicizz/jmqtt)

# 一、目标

1. 检测Jmqtt功能及性能运行情况
1. 为使用者提供参考

说明:以下测试为Jmqtt作者闲暇之时进行的测试,仅供参考,再线上使用Jmqtt时,请自行测试,感谢 朱霄 同学提供的服务器等测试资源

# 二、测试机配置

|                 | 数量        | 操作系统        | 配置                          |
| --------------- | ----------- | --------------- | ----------------------------- |
| Jmqtt运行服务器 | 2台(集群) | linux centos 7  | 8C16G                         |
| Jmqtt压测机     | 2台         | linux centos 7  | 8C16G                         |
| Mysql           | 单库单表    | mysql5.7        | 阿里云rds基础版:ESSD OL1云盘 |
| SLB             | 1           | 支持4层负载均衡 | 支持20万长连接                |

测试脚本:

1. jmeter
1. emqx-bench

# 三、功能测试报告

| 功能项                      | 是否满足 | 备注                                                      |
| --------------------------- | -------- | --------------------------------------------------------- |
| 集群连接                    | ✅        |                                                           |
| 集群cleansession为false连接 | ✅        |                                                           |
| 设备在集群互发消息          | ✅        |                                                           |
| retain消息                  | ✅        |                                                           |
| will消息                    | ✅        |                                                           |
| qos0                        | ✅        |                                                           |
| qos1                        | ✅        |                                                           |
| qos2                        | ✅        |                                                           |
| 离线消息                    | ✅        | 不限离线消息数量                                          |
| 设备信息持久化              | ✅        | 见sql表:jmqtt_session                                    |
| 订阅关系持久化              | ✅        | 见sql表:jmqtt_subscription                               |
| 消息持久化                  | ✅        | 见sql表:jmqtt_message                                    |
| 集群事件消息                | ✅        | 包含集群间转发消息,连接事件,见sql表:jmqtt_cluser_event |
| 设备消息接收状态            | ✅        | 见sql表:jmqtt_client_inbox                               |
| 监控topic                   | ❎        | 不支持,需自行实现                                        |
| 各个消息桥接                | ❎        | 不支持,需自行实现                                        |
| 规则引擎                    | ❎        | 不支持,需自行实现                                        |

# 四、单机性能测试报告

## 4.1 连接数性能报告

连接数单机测试达到10W级连接,连接tps1000,未出现报错,
注意:因测试机资源问题,未压测到上限。详细截图报告见下图

### 4.1.1 连接数200,连接持续3min

![image.png](https://cdn.nlark.com/yuque/0/2022/png/2726385/1642746010873-3806f744-eebd-4a06-b638-a5c9030a913a.png#clientId=u63915b68-034c-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=111&id=u3f625a5c&margin=%5Bobject%20Object%5D&name=image.png&originHeight=222&originWidth=1704&originalType=binary&ratio=1&rotation=0&showTitle=false&size=129250&status=done&style=none&taskId=u1e953a0b-157d-45d3-9bd0-886c7d1982c&title=&width=852)

### 4.1.2 连接数1000,连接持续3min

![image.png](https://cdn.nlark.com/yuque/0/2022/png/2726385/1642746046914-ac14a7be-c248-4dfd-9244-3b3b6bf50bfd.png#clientId=u63915b68-034c-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=114&id=ude67f41c&margin=%5Bobject%20Object%5D&name=image.png&originHeight=228&originWidth=1754&originalType=binary&ratio=1&rotation=0&showTitle=false&size=113132&status=done&style=none&taskId=u96d8e2f0-5487-4386-a565-467f58186d3&title=&width=877)

### 4.1.3 连接数2000,连接持续3min

![image.png](https://cdn.nlark.com/yuque/0/2022/png/2726385/1642746065295-8a158413-36ac-4e88-b67c-1c6f6f565bc7.png#clientId=u63915b68-034c-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=313&id=u67a9620e&margin=%5Bobject%20Object%5D&name=image.png&originHeight=626&originWidth=2236&originalType=binary&ratio=1&rotation=0&showTitle=false&size=390748&status=done&style=none&taskId=u44a649b4-aa01-4ad7-906e-e55f1b7f21e&title=&width=1118)

### 4.1.4 连接数5000,连接持续3min

![image.png](https://cdn.nlark.com/yuque/0/2022/png/2726385/1642746088650-4e2c8fab-4185-41db-b406-89571492fb79.png#clientId=u63915b68-034c-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=279&id=u8a9a70e7&margin=%5Bobject%20Object%5D&name=image.png&originHeight=558&originWidth=2192&originalType=binary&ratio=1&rotation=0&showTitle=false&size=257112&status=done&style=none&taskId=u68f785bb-6e9b-4668-9fa0-23ce4914e1a&title=&width=1096)

### 4.1.5 连接数1W,连接持续3min

![image.png](https://cdn.nlark.com/yuque/0/2022/png/2726385/1642746108655-757645fb-6951-4dc4-ac6e-97946f35e980.png#clientId=u63915b68-034c-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=42&id=u4b3542f2&margin=%5Bobject%20Object%5D&name=image.png&originHeight=84&originWidth=945&originalType=binary&ratio=1&rotation=0&showTitle=false&size=49950&status=done&style=none&taskId=ue2cf34cc-2cef-4ef0-88db-3826ecda806&title=&width=472.5)

### 4.1.6 连接数2W,连接持续3min

超过2W采用开源emqx-bench进行性能压测
![image.png](https://cdn.nlark.com/yuque/0/2022/png/2726385/1642746146826-bc37501b-4b08-4566-a461-02ced5d72cfe.png#clientId=u63915b68-034c-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=23&id=ue6ccf234&margin=%5Bobject%20Object%5D&name=image.png&originHeight=46&originWidth=634&originalType=binary&ratio=1&rotation=0&showTitle=false&size=22600&status=done&style=none&taskId=u33aaf8db-a098-40a5-8036-e6f2065e970&title=&width=317)

### 4.1.7 连接数5W,连接持续10min

![image.png](https://cdn.nlark.com/yuque/0/2022/png/2726385/1642746200835-34532619-4370-4a54-9f57-607b6eb20916.png#clientId=u63915b68-034c-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=20&id=ub9254472&margin=%5Bobject%20Object%5D&name=image.png&originHeight=39&originWidth=738&originalType=binary&ratio=1&rotation=0&showTitle=false&size=18207&status=done&style=none&taskId=ud0a3ce29-ca11-482e-83f5-71b4c49f78c&title=&width=369)

### 4.1.8 连接数10W,连接持续10min

压到10W后,未持续压测,尚未压到连接数上限,两台测试机截图如下:
![image.png](https://cdn.nlark.com/yuque/0/2022/png/2726385/1642746254255-83829c1c-0ebc-46cb-a252-68febb726b2f.png#clientId=u63915b68-034c-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=24&id=u55c0540d&margin=%5Bobject%20Object%5D&name=image.png&originHeight=48&originWidth=1491&originalType=binary&ratio=1&rotation=0&showTitle=false&size=43616&status=done&style=none&taskId=ue7fdb9c3-50d5-4e16-9f5e-c70b6b430b0&title=&width=745.5)
服务器load截图:
![image.png](https://cdn.nlark.com/yuque/0/2022/png/2726385/1642746280704-1784ae08-ffeb-4fdd-94f1-98206c444c54.png#clientId=u63915b68-034c-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=70&id=udcb79ca0&margin=%5Bobject%20Object%5D&name=image.png&originHeight=140&originWidth=716&originalType=binary&ratio=1&rotation=0&showTitle=false&size=71157&status=done&style=none&taskId=u6a9c56d6-706e-4f0e-9813-5c3bf2a1adb&title=&width=358)

## 4.2 发送消息性能报告

### 4.2.1 设备连接2W,再启动1000连接持续发送消息

消息大小:256byte
qos:0
每隔10ms发送1条消息
![image.png](https://cdn.nlark.com/yuque/0/2022/png/2726385/1642747253961-4180a124-1185-4aac-8442-781b044fb609.png#clientId=u63915b68-034c-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=251&id=ua5ae4796&margin=%5Bobject%20Object%5D&name=image.png&originHeight=302&originWidth=438&originalType=binary&ratio=1&rotation=0&showTitle=false&size=104022&status=done&style=none&taskId=u06b96fe2-eac6-404c-a96b-5f353408b1f&title=&width=364)
对比emq服务(没有2W长连接设备保持):broker.emqx.io
![image.png](https://cdn.nlark.com/yuque/0/2022/png/2726385/1642747299016-50f125b1-fe67-4379-8858-37ac0cdec74b.png#clientId=u63915b68-034c-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=203&id=u73d17b7e&margin=%5Bobject%20Object%5D&name=image.png&originHeight=255&originWidth=459&originalType=binary&ratio=1&rotation=0&showTitle=false&size=85760&status=done&style=none&taskId=u61141867-fb4f-4047-ae97-9400e851490&title=&width=364.5)

### 4.2.2 设备连接2W,再启动200连接持续发送消息

消息大小:256byte
qos:1
每隔10ms发送1条消息
![image.png](https://cdn.nlark.com/yuque/0/2022/png/2726385/1642747478891-b7ec2f4a-012d-4c9d-9dbc-f2bf7112cae8.png#clientId=u63915b68-034c-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=203&id=ue0cc314a&margin=%5Bobject%20Object%5D&name=image.png&originHeight=284&originWidth=403&originalType=binary&ratio=1&rotation=0&showTitle=false&size=91808&status=done&style=none&taskId=u236295c7-4c40-4247-b975-e31b2558b3b&title=&width=287.5)

### 4.2.3 设备连接2W,再启动200连接持续发送消息

消息大小:100byte
qos:1
每隔10ms发送1条消息
![image.png](https://cdn.nlark.com/yuque/0/2022/png/2726385/1642747586254-5d5b40f9-4cef-4633-9a3d-94ab46837bd6.png#clientId=u63915b68-034c-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=176&id=uf4feec24&margin=%5Bobject%20Object%5D&name=image.png&originHeight=301&originWidth=561&originalType=binary&ratio=1&rotation=0&showTitle=false&size=99190&status=done&style=none&taskId=u7428afd4-7a26-4449-9433-47fc1af1230&title=&width=327.5)

## 4.3 订阅性能报告

### 4.3.1 启动2W个设备,订阅2W个topic

### ![image.png](https://cdn.nlark.com/yuque/0/2022/png/2726385/1642747707443-c70722d5-e4f6-4674-a9ab-3ba8490899d9.png#clientId=u63915b68-034c-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=255&id=u22f47aba&margin=%5Bobject%20Object%5D&name=image.png&originHeight=333&originWidth=285&originalType=binary&ratio=1&rotation=0&showTitle=false&size=80501&status=done&style=none&taskId=u39a72cd5-3b32-4b86-9490-28e3815563d&title=&width=218.5) 

# 五、集群性能测试报告

## 5.1 连接数性能报告

Jmqtt服务器两台,设备连接数10W,未压测到上限
![image.png](https://cdn.nlark.com/yuque/0/2022/png/2726385/1642748056231-f1d72eeb-0dac-4e33-8ba2-0111e9f11387.png#clientId=u63915b68-034c-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=314&id=u09e9c4d3&margin=%5Bobject%20Object%5D&name=image.png&originHeight=628&originWidth=1928&originalType=binary&ratio=1&rotation=0&showTitle=false&size=589189&status=done&style=none&taskId=ue4369d89-e4b7-4dac-979b-4514e76acdb&title=&width=964)

## 5.2 发送消息性能报告

发送消息强依赖db进行保存,性能瓶颈在db侧,故tps上不去

### 5.2.1 设备连接2W,启动200连接持续发送消息

消息大小:256byte
qos:1
![image.png](https://cdn.nlark.com/yuque/0/2022/png/2726385/1642748182652-85094ef1-740e-4067-85ac-1d089961994b.png#clientId=u63915b68-034c-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=271&id=u5a016123&margin=%5Bobject%20Object%5D&name=image.png&originHeight=542&originWidth=658&originalType=binary&ratio=1&rotation=0&showTitle=false&size=239858&status=done&style=none&taskId=udb8c34ff-f596-4ef8-bedc-5343205e3ab&title=&width=329)

### 5.2.2 设备连接2W,启动200连接持续发送消息

消息大小:100byte
qos:1
![image.png](https://cdn.nlark.com/yuque/0/2022/png/2726385/1642748243207-6bd4b28d-818c-4f71-9759-11edfe7ffbac.png#clientId=u63915b68-034c-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=251&id=ud1655849&margin=%5Bobject%20Object%5D&name=image.png&originHeight=502&originWidth=694&originalType=binary&ratio=1&rotation=0&showTitle=false&size=230671&status=done&style=none&taskId=u9e328faa-fd3f-48f8-884c-7104ba74757&title=&width=347)

## 5.3 订阅性能报告

### 5.3.1 启动2W个设备,订阅2W个topic

![image.png](https://cdn.nlark.com/yuque/0/2022/png/2726385/1642748307769-aba52973-ef70-4c53-b1d1-b0e8b81ab18f.png#clientId=u63915b68-034c-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=203&id=ua71378ae&margin=%5Bobject%20Object%5D&name=image.png&originHeight=406&originWidth=920&originalType=binary&ratio=1&rotation=0&showTitle=false&size=143298&status=done&style=none&taskId=u0c19253a-4c2b-4ecc-adef-b7ea8893f21&title=&width=460)

### 5.3.2 启动5W个设备,订阅5W个topic

![image.png](https://cdn.nlark.com/yuque/0/2022/png/2726385/1642748324754-a92522f8-395d-4f70-a2f1-ff143bb3bb37.png#clientId=u63915b68-034c-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=275&id=u048d5dd1&margin=%5Bobject%20Object%5D&name=image.png&originHeight=550&originWidth=718&originalType=binary&ratio=1&rotation=0&showTitle=false&size=183115&status=done&style=none&taskId=ue28001b0-b3a9-407d-9c6d-1fc15a813d3&title=&width=359)

# 六、性能测试说明

## 6.1 关于连接数

1. 单机和集群都未压测到上线,受限于时间和测试机问题
1. 实际使用时,单机连接数不要超过5w,方式服务器重启时,大量重连请求导致不可预知的问题

## 6.2 关于消息发送tps

1. 整体看消息tps与emq的测试服务器消息tps差不多
1. 为什么集群tps上不去?
   1. 因为消息保存强依赖mysql进行存储,mysql存储tps已达上限,这也是消息发送的可优化项

## 6.3 关于订阅

1. 目前订阅强依赖db存储,不存在订阅关系丢失的问题
1. 本地采用tri树进行订阅关系的管理

## 6.3 Jmqtt性能可优化项指南

1. 升级mysql
1. 集群事件转发器 用 mq替代(kafka或其他的mq都可以),减少集群服务器从db long pull的模式
1. 消息存储采用其他存储中间件,例如时序数据库,甚至kafka都行

# 七、测试常见问题

## 7.1 测试机需要修改端口限制,否则无法启动5W长连接

linux centos7默认限制了端口可用范围,需要修改一下,不然连接数无法达到5w
查看端口范围:cat /proc/sys/net/ipv4/ip_local_port_range

## 7.2 jmqtt服务器需要修改文件句柄数

linux 万物皆文件,需要修改文件句柄数,否则无法支持那么大的长连接
linux默认为65535个文件句柄数,需要修改两个地方:
ulimit -n 和vim /etc/security/limits.conf

## 7.3 jmqtt集群的负载均衡需要升级

mqtt协议的复杂均衡需要4层的负载均衡代理,
默认购买的SLB一般只支持5W长连接,故需要升级

# 八、附:Jmqtt启动问题

1. 目前jmqtt尽量减少各种依赖,代码简单,很容易进行二次开发和开箱即用
1. 请使用最新发布版本或master 分支代码
1. 建议从源码构建
1. 本地启动,直接在BrokerStartup执行main方法

## 8.1 结构介绍

![image.png](https://cdn.nlark.com/yuque/0/2022/png/2726385/1642749307002-ed2ad593-44d3-4dac-a62e-f9db215e5fe5.png#clientId=u63915b68-034c-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=312&id=u7cac4a06&margin=%5Bobject%20Object%5D&name=image.png&originHeight=1290&originWidth=1886&originalType=binary&ratio=1&rotation=0&showTitle=false&size=1369802&status=done&style=none&taskId=ua523a209-b0b0-4450-965a-f60ff3ca631&title=&width=456)

## 8.2 在db库中初始化好脚本
t默认使用的是mysql驱动,依赖其他db需要自行修改

1. 在自己的库中,执行jmqtt.sql
1. 执行后如截图所示:

![image.png](https://cdn.nlark.com/yuque/0/2022/png/2726385/1642749458236-93fdeb96-9e87-40d7-9152-cdfd8dfbe1bb.png#clientId=u63915b68-034c-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=309&id=ud2bd07e1&margin=%5Bobject%20Object%5D&name=image.png&originHeight=662&originWidth=594&originalType=binary&ratio=1&rotation=0&showTitle=false&size=150608&status=done&style=none&taskId=u74608728-bee1-4b58-acc8-e64fb9d1b8d&title=&width=277)

## 8.3 打包

在broker模块下,执行 :mvn -Ppackage-all -DskipTests clean install -U  
打包后:
![image.png](https://cdn.nlark.com/yuque/0/2022/png/2726385/1642749594691-b2ac7265-1947-4694-b834-8285f5acc1da.png#clientId=u63915b68-034c-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=360&id=u9e9f6e8c&margin=%5Bobject%20Object%5D&name=image.png&originHeight=720&originWidth=1374&originalType=binary&ratio=1&rotation=0&showTitle=false&size=640584&status=done&style=none&taskId=ua743bfc9-2d04-4761-8c85-d8a5b18b6f5&title=&width=687)

## 8.4 修改配置文件

如截图:这里修改为自己的db连接串
![image.png](https://cdn.nlark.com/yuque/0/2022/png/2726385/1642749722171-23d6363a-9bcd-425a-a682-c36d878cdca2.png#clientId=u63915b68-034c-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=278&id=u60f8b443&margin=%5Bobject%20Object%5D&name=image.png&originHeight=1242&originWidth=2758&originalType=binary&ratio=1&rotation=0&showTitle=false&size=1430587&status=done&style=none&taskId=u54a006e0-9619-468f-9deb-2a45f16b9de&title=&width=617)

## 8.5 上传资源到服务器

1. 将jar,conf下的资源,bin下的脚本都上传到服务器:

![image.png](https://cdn.nlark.com/yuque/0/2022/png/2726385/1642749874476-b29816f0-2c04-4ab5-bac2-2f771e8c4130.png#clientId=u63915b68-034c-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=80&id=u809bdae2&margin=%5Bobject%20Object%5D&name=image.png&originHeight=160&originWidth=1314&originalType=binary&ratio=1&rotation=0&showTitle=false&size=131296&status=done&style=none&taskId=u3491c4a5-645f-4ae6-bc72-10edd16469b&title=&width=657)
其中 config为conf下的配置文件

2. 执行启动命令:./runbroker.sh jmqtt-broker-3.0.0.jar -h config/

![image.png](https://cdn.nlark.com/yuque/0/2022/png/2726385/1642749926168-c1ff93fb-523f-4569-b6e6-231db8d3c50c.png#clientId=u63915b68-034c-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=62&id=u440600e3&margin=%5Bobject%20Object%5D&name=image.png&originHeight=124&originWidth=1226&originalType=binary&ratio=1&rotation=0&showTitle=false&size=91843&status=done&style=none&taskId=ue93dc841-1df2-4ae5-bbf9-3cf8117dfc0&title=&width=613)

3. 查看启动日志:
   1. cd jmqttlogs
   1. tailf -200 brokerLog.log : 显示如下截图说明启动成功

![image.png](https://cdn.nlark.com/yuque/0/2022/png/2726385/1642749978459-d4df9171-0c11-4e05-a877-dc3eeeed2fa8.png#clientId=u63915b68-034c-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=168&id=ucb54e4fc&margin=%5Bobject%20Object%5D&name=image.png&originHeight=336&originWidth=2268&originalType=binary&ratio=1&rotation=0&showTitle=false&size=259176&status=done&style=none&taskId=uafca868b-afe8-45a8-b2bb-42fa7003c9e&title=&width=1134)
![image.png](https://cdn.nlark.com/yuque/0/2022/png/2726385/1642750011254-87d99be8-40f4-40a8-bbaf-8a3cb67f8f14.png#clientId=u63915b68-034c-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=67&id=u9268067d&margin=%5Bobject%20Object%5D&name=image.png&originHeight=134&originWidth=1308&originalType=binary&ratio=1&rotation=0&showTitle=false&size=61787&status=done&style=none&taskId=ub9ebb91e-a64c-4e63-8601-48e82931c6e&title=&width=654)

jmqt


================================================
FILE: jmqtt-doc/pom.xml
================================================
<?xml version="1.0" encoding="UTF-8"?>

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>jmqtt</artifactId>
        <groupId>org.jmqtt</groupId>
        <version>3.0.0</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>jmqtt-doc</artifactId>

    <name>jmqtt-doc</name>


    <properties>

    </properties>

    <dependencies>

    </dependencies>
</project>


================================================
FILE: jmqtt-example/pom.xml
================================================
<?xml version="1.0" encoding="UTF-8"?>

<project xmlns="http://maven.apache.org/POM/4.0
Download .txt
gitextract_nylx23xb/

├── .dockerignore
├── .gitignore
├── Dockerfile
├── LICENSE
├── README.md
├── README_CN.md
├── jmqtt-acceptance/
│   ├── README.md
│   └── pom.xml
├── jmqtt-admin/
│   └── pom.xml
├── jmqtt-broker/
│   ├── pom.xml
│   └── src/
│       ├── main/
│       │   ├── java/
│       │   │   └── org/
│       │   │       └── jmqtt/
│       │   │           └── broker/
│       │   │               ├── BrokerController.java
│       │   │               └── BrokerStartup.java
│       │   └── resources/
│       │       ├── bin/
│       │       │   ├── jmqttshutdown
│       │       │   └── runbroker.sh
│       │       └── conf/
│       │           ├── client.cer
│       │           ├── jmqtt.properties
│       │           ├── jmqtt.sql
│       │           ├── log4j2.xml
│       │           └── server.pfx
│       └── test/
│           └── java/
│               └── org/
│                   └── jmqtt/
│                       └── broker/
│                           └── test/
│                               └── Runtime.java
├── jmqtt-bus/
│   ├── README.md
│   ├── pom.xml
│   └── src/
│       ├── main/
│       │   └── java/
│       │       └── org/
│       │           └── jmqtt/
│       │               └── bus/
│       │                   ├── Authenticator.java
│       │                   ├── BusController.java
│       │                   ├── ClusterEventManager.java
│       │                   ├── DeviceMessageManager.java
│       │                   ├── DeviceSessionManager.java
│       │                   ├── DeviceSubscriptionManager.java
│       │                   ├── enums/
│       │                   │   ├── ClusterEventCodeEnum.java
│       │                   │   ├── DeviceOnlineStateEnum.java
│       │                   │   ├── MessageAckEnum.java
│       │                   │   ├── MessageFlowEnum.java
│       │                   │   ├── MessageSourceEnum.java
│       │                   │   └── TransportProtocolEnum.java
│       │                   ├── event/
│       │                   │   ├── EventCenter.java
│       │                   │   └── GatewayListener.java
│       │                   ├── impl/
│       │                   │   ├── ClusterEventManagerImpl.java
│       │                   │   ├── DefaultAuthenticator.java
│       │                   │   ├── DeviceMessageManagerImpl.java
│       │                   │   ├── DeviceSessionManagerImpl.java
│       │                   │   └── DeviceSubscriptionManagerImpl.java
│       │                   ├── model/
│       │                   │   ├── ClusterEvent.java
│       │                   │   ├── DeviceInboxMessage.java
│       │                   │   ├── DeviceMessage.java
│       │                   │   ├── DeviceSession.java
│       │                   │   └── DeviceSubscription.java
│       │                   ├── store/
│       │                   │   ├── AbstractDBStore.java
│       │                   │   ├── DBCallback.java
│       │                   │   ├── DBUtils.java
│       │                   │   ├── daoobject/
│       │                   │   │   ├── DeviceInboxMessageDO.java
│       │                   │   │   ├── EventDO.java
│       │                   │   │   ├── MessageDO.java
│       │                   │   │   ├── RetainMessageDO.java
│       │                   │   │   ├── SessionDO.java
│       │                   │   │   └── SubscriptionDO.java
│       │                   │   └── mapper/
│       │                   │       ├── ClientInboxMessageMapper.java
│       │                   │       ├── EventMapper.java
│       │                   │       ├── MessageMapper.java
│       │                   │       ├── RetainMessageMapper.java
│       │                   │       ├── SessionMapper.java
│       │                   │       └── SubscriptionMapper.java
│       │                   └── subscription/
│       │                       ├── CNode.java
│       │                       ├── CTrie.java
│       │                       ├── CTrieSubscriptionMatcher.java
│       │                       ├── DefaultSubscriptionTreeMatcher.java
│       │                       ├── DumpTreeVisitor.java
│       │                       ├── INode.java
│       │                       ├── SubscriptionCounterVisitor.java
│       │                       ├── SubscriptionMatcher.java
│       │                       ├── TNode.java
│       │                       ├── Token.java
│       │                       ├── Topic.java
│       │                       └── model/
│       │                           └── Subscription.java
│       └── test/
│           └── java/
│               └── org/
│                   └── jmqtt/
│                       ├── AppTest.java
│                       └── bus/
│                           └── subscription/
│                               ├── CTrieSubscriptionMatcherMatchingTest.java
│                               ├── CTrieTest.java
│                               └── TopicTest.java
├── jmqtt-doc/
│   ├── README.md
│   ├── docs/
│   │   ├── README.md
│   │   └── TEST_REPORT.md
│   └── pom.xml
├── jmqtt-example/
│   ├── pom.xml
│   └── src/
│       └── main/
│           └── java/
│               └── org/
│                   └── jmqtt/
│                       ├── java/
│                       │   ├── Consumer.java
│                       │   └── Producer.java
│                       └── websocket/
│                           ├── paho-mqtt-min.js
│                           ├── paho-mqtt.js
│                           └── webSocket.html
├── jmqtt-manager
├── jmqtt-mqtt/
│   ├── README.md
│   ├── pom.xml
│   └── src/
│       └── main/
│           └── java/
│               └── org/
│                   └── jmqtt/
│                       └── mqtt/
│                           ├── ConnectManager.java
│                           ├── MQTTConnection.java
│                           ├── MQTTConnectionFactory.java
│                           ├── MQTTServer.java
│                           ├── codec/
│                           │   ├── ByteBuf2WebSocketEncoder.java
│                           │   └── WebSocket2ByteBufDecoder.java
│                           ├── event/
│                           │   └── MqttEventListener.java
│                           ├── model/
│                           │   └── MqttTopic.java
│                           ├── netty/
│                           │   ├── MqttNettyUtils.java
│                           │   ├── MqttRemotingServer.java
│                           │   ├── NettyMqttHandler.java
│                           │   └── NettySslHandler.java
│                           ├── protocol/
│                           │   ├── RequestProcessor.java
│                           │   └── impl/
│                           │       ├── ConnectProcessor.java
│                           │       ├── DisconnectProcessor.java
│                           │       ├── PingProcessor.java
│                           │       ├── PubAckProcessor.java
│                           │       ├── PubCompProcessor.java
│                           │       ├── PubRecProcessor.java
│                           │       ├── PubRelProcessor.java
│                           │       ├── PublishProcessor.java
│                           │       ├── SubscribeProcessor.java
│                           │       └── UnSubscribeProcessor.java
│                           ├── retain/
│                           │   ├── RetainMessageHandler.java
│                           │   └── impl/
│                           │       └── RetainMessageHandlerImpl.java
│                           ├── session/
│                           │   └── MqttSession.java
│                           └── utils/
│                               ├── MqttMessageUtil.java
│                               └── MqttMsgHeader.java
├── jmqtt-support/
│   ├── README.md
│   ├── pom.xml
│   └── src/
│       └── main/
│           └── java/
│               └── org/
│                   └── jmqtt/
│                       └── support/
│                           ├── config/
│                           │   ├── BrokerConfig.java
│                           │   └── NettyConfig.java
│                           ├── exception/
│                           │   ├── RemotingConnectException.java
│                           │   ├── RemotingException.java
│                           │   ├── RemotingSendRequestException.java
│                           │   ├── RemotingTimeoutException.java
│                           │   └── RemotingTooMuchRequestException.java
│                           ├── helper/
│                           │   ├── MixAll.java
│                           │   ├── Pair.java
│                           │   ├── RejectHandler.java
│                           │   └── ThreadFactoryImpl.java
│                           ├── log/
│                           │   ├── JmqttLogger.java
│                           │   └── LogUtil.java
│                           └── remoting/
│                               ├── RemotingHelper.java
│                               └── RemotingService.java
├── jmqtt-tcp/
│   ├── README.md
│   ├── pom.xml
│   └── src/
│       └── main/
│           └── java/
│               └── org/
│                   └── jmqtt/
│                       └── App.java
└── pom.xml
Download .txt
SYMBOL INDEX (896 symbols across 107 files)

FILE: jmqtt-broker/src/main/java/org/jmqtt/broker/BrokerController.java
  class BrokerController (line 14) | public class BrokerController {
    method BrokerController (line 29) | public BrokerController(BrokerConfig brokerConfig, NettyConfig nettyCo...
    method start (line 36) | public void start(){
    method shutdown (line 43) | public void shutdown(){

FILE: jmqtt-broker/src/main/java/org/jmqtt/broker/BrokerStartup.java
  class BrokerStartup (line 22) | public class BrokerStartup {
    method main (line 24) | public static void main(String[] args) {
    method start (line 34) | public static BrokerController start(String[] args) throws Exception {
    method buildOptions (line 88) | private static Options buildOptions(){
    method initConfig (line 111) | private static void initConfig(String jmqttConfigPath, BrokerConfig br...

FILE: jmqtt-broker/src/main/resources/conf/jmqtt.sql
  type `jmqtt_session` (line 15) | CREATE TABLE `jmqtt_session` (
  type `jmqtt_subscription` (line 29) | CREATE TABLE `jmqtt_subscription` (
  type `jmqtt_message` (line 39) | CREATE TABLE `jmqtt_message` (
  type `jmqtt_client_inbox` (line 51) | CREATE TABLE `jmqtt_client_inbox` (
  type `jmqtt_retain_message` (line 62) | CREATE TABLE `jmqtt_retain_message` (
  type `jmqtt_cluster_event` (line 73) | CREATE TABLE `jmqtt_cluster_event` (

FILE: jmqtt-broker/src/test/java/org/jmqtt/broker/test/Runtime.java
  class Runtime (line 8) | public class Runtime {
    method subscribe (line 10) | @Test

FILE: jmqtt-bus/src/main/java/org/jmqtt/bus/Authenticator.java
  type Authenticator (line 4) | public interface Authenticator {
    method login (line 7) | boolean login(String clientId,String userName,byte[] password);
    method onBlackList (line 9) | boolean onBlackList(String clientId,String remoteIpAddress);
    method clientIdVerify (line 11) | boolean clientIdVerify(String clientId);
    method subscribeVerify (line 13) | boolean subscribeVerify(String clientId,String topic);

FILE: jmqtt-bus/src/main/java/org/jmqtt/bus/BusController.java
  class BusController (line 9) | public class BusController {
    method BusController (line 19) | public BusController(BrokerConfig brokerConfig){
    method start (line 31) | public void start(){
    method shutdown (line 36) | public void shutdown(){
    method getAuthenticator (line 41) | public Authenticator getAuthenticator() {
    method getDeviceSessionManager (line 45) | public DeviceSessionManager getDeviceSessionManager() {
    method getDeviceMessageManager (line 49) | public DeviceMessageManager getDeviceMessageManager() {
    method getDeviceSubscriptionManager (line 53) | public DeviceSubscriptionManager getDeviceSubscriptionManager() {
    method getClusterEventManager (line 57) | public ClusterEventManager getClusterEventManager() {
    method getSubscriptionMatcher (line 61) | public SubscriptionMatcher getSubscriptionMatcher() {

FILE: jmqtt-bus/src/main/java/org/jmqtt/bus/ClusterEventManager.java
  type ClusterEventManager (line 9) | public interface ClusterEventManager {
    method sendEvent (line 11) | void sendEvent(ClusterEvent clusterEvent);
    method registerEventListener (line 13) | void registerEventListener(GatewayListener listener);
    method start (line 15) | void start();
    method shutdown (line 17) | void shutdown();

FILE: jmqtt-bus/src/main/java/org/jmqtt/bus/DeviceMessageManager.java
  type DeviceMessageManager (line 9) | public interface DeviceMessageManager {
    method clearUnAckMessage (line 11) | void clearUnAckMessage(String clientId);
    method dispatcher (line 17) | void dispatcher(DeviceMessage deviceMessage);
    method storeMessage (line 19) | Long storeMessage(DeviceMessage deviceMessage);
    method queryUnAckMessages (line 21) | List<DeviceMessage> queryUnAckMessages(String clientId,int limit);
    method queryByIds (line 23) | List<DeviceMessage> queryByIds(List<Long> ids);
    method addClientInBoxMsg (line 25) | Long addClientInBoxMsg(String clientId,Long messageId, MessageAckEnum ...
    method ackMessage (line 27) | boolean ackMessage(String clientId,Long messageId);

FILE: jmqtt-bus/src/main/java/org/jmqtt/bus/DeviceSessionManager.java
  type DeviceSessionManager (line 6) | public interface DeviceSessionManager {
    method getSession (line 10) | DeviceSession getSession(String clientId);
    method storeSession (line 12) | void storeSession(DeviceSession deviceSession);
    method offline (line 15) | void offline(String clientId);

FILE: jmqtt-bus/src/main/java/org/jmqtt/bus/DeviceSubscriptionManager.java
  type DeviceSubscriptionManager (line 7) | public interface DeviceSubscriptionManager {
    method subscribe (line 15) | boolean subscribe(DeviceSubscription deviceSubscription);
    method unSubscribe (line 21) | boolean unSubscribe(String clientId,String topic);
    method isMatch (line 23) | boolean isMatch(String pubTopic,String subTopic);
    method onlySubscribe2Tree (line 25) | boolean onlySubscribe2Tree(DeviceSubscription deviceSubscription);
    method onlyUnUnSubscribeFromTree (line 27) | boolean onlyUnUnSubscribeFromTree(String clientId,String topic);
    method getAllSubscription (line 29) | Set<DeviceSubscription> getAllSubscription(String clientId);
    method deleteAllSubscription (line 31) | void deleteAllSubscription(String clientId);

FILE: jmqtt-bus/src/main/java/org/jmqtt/bus/enums/ClusterEventCodeEnum.java
  type ClusterEventCodeEnum (line 6) | public enum ClusterEventCodeEnum {
    method ClusterEventCodeEnum (line 15) | ClusterEventCodeEnum(String code) {
    method getCode (line 19) | public String getCode() {

FILE: jmqtt-bus/src/main/java/org/jmqtt/bus/enums/DeviceOnlineStateEnum.java
  type DeviceOnlineStateEnum (line 4) | public enum DeviceOnlineStateEnum {
    method DeviceOnlineStateEnum (line 14) | DeviceOnlineStateEnum(String code) {
    method getCode (line 18) | public String getCode() {

FILE: jmqtt-bus/src/main/java/org/jmqtt/bus/enums/MessageAckEnum.java
  type MessageAckEnum (line 4) | public enum MessageAckEnum {
    method MessageAckEnum (line 14) | MessageAckEnum(int code) {
    method getCode (line 18) | public int getCode() {
    method getByCode (line 22) | public static MessageAckEnum getByCode(int code) {

FILE: jmqtt-bus/src/main/java/org/jmqtt/bus/enums/MessageFlowEnum.java
  type MessageFlowEnum (line 4) | public enum MessageFlowEnum {
    method MessageFlowEnum (line 14) | MessageFlowEnum(String code) {
    method getCode (line 18) | public String getCode() {

FILE: jmqtt-bus/src/main/java/org/jmqtt/bus/enums/MessageSourceEnum.java
  type MessageSourceEnum (line 4) | public enum MessageSourceEnum {
    method MessageSourceEnum (line 14) | MessageSourceEnum(String code) {
    method getCode (line 18) | public String getCode() {

FILE: jmqtt-bus/src/main/java/org/jmqtt/bus/enums/TransportProtocolEnum.java
  type TransportProtocolEnum (line 5) | public enum TransportProtocolEnum {
    method TransportProtocolEnum (line 16) | TransportProtocolEnum(String code) {
    method getCode (line 20) | public String getCode() {

FILE: jmqtt-bus/src/main/java/org/jmqtt/bus/event/EventCenter.java
  class EventCenter (line 6) | public class EventCenter {
    method EventCenter (line 10) | public EventCenter(){
    method register (line 18) | public void register(GatewayListener listener){
    method sendEvent (line 26) | public void sendEvent(ClusterEvent clusterEvent){

FILE: jmqtt-bus/src/main/java/org/jmqtt/bus/event/GatewayListener.java
  type GatewayListener (line 10) | public interface GatewayListener {
    method consume (line 12) | void consume(ClusterEvent clusterEvent);

FILE: jmqtt-bus/src/main/java/org/jmqtt/bus/impl/ClusterEventManagerImpl.java
  class ClusterEventManagerImpl (line 34) | public class ClusterEventManagerImpl implements ClusterEventManager {
    method ClusterEventManagerImpl (line 44) | public ClusterEventManagerImpl(SubscriptionMatcher subscriptionMatcher){
    method start (line 49) | @Override
    method shutdown (line 84) | @Override
    method consumeEvent (line 92) | private void consumeEvent(ClusterEvent event){
    method pollEvent (line 117) | private List<ClusterEvent> pollEvent(int maxPollNum) {
    method sendEvent (line 149) | @Override
    method registerEventListener (line 161) | public void registerEventListener(GatewayListener listener){
    method convert (line 166) | private EventDO convert(ClusterEvent clusterEvent) {

FILE: jmqtt-bus/src/main/java/org/jmqtt/bus/impl/DefaultAuthenticator.java
  class DefaultAuthenticator (line 6) | public class DefaultAuthenticator implements Authenticator {
    method login (line 8) | @Override
    method onBlackList (line 13) | @Override
    method clientIdVerify (line 18) | @Override
    method subscribeVerify (line 23) | @Override

FILE: jmqtt-bus/src/main/java/org/jmqtt/bus/impl/DeviceMessageManagerImpl.java
  class DeviceMessageManagerImpl (line 26) | public class DeviceMessageManagerImpl implements DeviceMessageManager {
    method clearUnAckMessage (line 30) | @Override
    method dispatcher (line 40) | @Override
    method storeMessage (line 76) | @Override
    method queryUnAckMessages (line 88) | @Override
    method queryByIds (line 107) | @Override
    method addClientInBoxMsg (line 124) | @Override
    method ackMessage (line 142) | @Override
    method convert2Event (line 153) | private EventDO convert2Event(DeviceMessage deviceMessage) {
    method convert (line 162) | private MessageDO convert(DeviceMessage deviceMessage) {
    method convert (line 175) | private DeviceMessage convert(MessageDO messageDO) {
    method convert (line 192) | private DeviceInboxMessage convert(DeviceInboxMessageDO deviceInboxMes...

FILE: jmqtt-bus/src/main/java/org/jmqtt/bus/impl/DeviceSessionManagerImpl.java
  class DeviceSessionManagerImpl (line 16) | public class DeviceSessionManagerImpl implements DeviceSessionManager {
    method getSession (line 18) | @Override
    method storeSession (line 34) | @Override
    method offline (line 45) | @Override
    method convert (line 59) | private DeviceSession convert(SessionDO sessionDO) {
    method convert (line 75) | private SessionDO convert(DeviceSession deviceSession) {

FILE: jmqtt-bus/src/main/java/org/jmqtt/bus/impl/DeviceSubscriptionManagerImpl.java
  class DeviceSubscriptionManagerImpl (line 23) | public class DeviceSubscriptionManagerImpl implements DeviceSubscription...
    method DeviceSubscriptionManagerImpl (line 29) | public DeviceSubscriptionManagerImpl(SubscriptionMatcher subscriptionM...
    method subscribe (line 33) | @Override
    method unSubscribe (line 66) | @Override
    method isMatch (line 97) | @Override
    method onlySubscribe2Tree (line 103) | @Override
    method onlyUnUnSubscribeFromTree (line 109) | @Override
    method getAllSubscription (line 114) | @Override
    method deleteAllSubscription (line 132) | @Override
    method convert2Sub (line 142) | private Subscription convert2Sub(DeviceSubscription deviceSubscription){
    method convert (line 146) | private DeviceSubscription convert(SubscriptionDO subscriptionDO){
    method convert (line 157) | private SubscriptionDO convert(DeviceSubscription deviceSubscription){

FILE: jmqtt-bus/src/main/java/org/jmqtt/bus/model/ClusterEvent.java
  class ClusterEvent (line 8) | public class ClusterEvent {
    method getId (line 25) | public Long getId() {
    method setId (line 29) | public void setId(Long id) {
    method getContent (line 33) | public String getContent() {
    method setContent (line 37) | public void setContent(String content) {
    method getGmtCreate (line 41) | public Date getGmtCreate() {
    method setGmtCreate (line 45) | public void setGmtCreate(Date gmtCreate) {
    method getNodeIp (line 49) | public String getNodeIp() {
    method setNodeIp (line 53) | public void setNodeIp(String nodeIp) {
    method getClusterEventCode (line 57) | public ClusterEventCodeEnum getClusterEventCode() {
    method setClusterEventCode (line 61) | public void setClusterEventCode(ClusterEventCodeEnum clusterEventCode) {
    method getSubscription (line 65) | public Subscription getSubscription() {
    method setSubscription (line 69) | public void setSubscription(Subscription subscription) {
    method clone (line 73) | public ClusterEvent clone() {

FILE: jmqtt-bus/src/main/java/org/jmqtt/bus/model/DeviceInboxMessage.java
  class DeviceInboxMessage (line 5) | public class DeviceInboxMessage {
    method getId (line 19) | public Long getId() {
    method setId (line 23) | public void setId(Long id) {
    method getClientId (line 27) | public String getClientId() {
    method setClientId (line 31) | public void setClientId(String clientId) {
    method getMessageId (line 35) | public Long getMessageId() {
    method setMessageId (line 39) | public void setMessageId(Long messageId) {
    method getAck (line 43) | public Integer getAck() {
    method setAck (line 47) | public void setAck(Integer ack) {
    method getStoredTime (line 51) | public Date getStoredTime() {
    method setStoredTime (line 55) | public void setStoredTime(Date storedTime) {
    method getAckTime (line 59) | public Date getAckTime() {
    method setAckTime (line 63) | public void setAckTime(Date ackTime) {

FILE: jmqtt-bus/src/main/java/org/jmqtt/bus/model/DeviceMessage.java
  class DeviceMessage (line 9) | public class DeviceMessage {
    method getProperty (line 25) | public <T> T  getProperty(String key){
    method getId (line 32) | public Long getId() {
    method setId (line 36) | public void setId(Long id) {
    method getContent (line 40) | public byte[] getContent() {
    method setContent (line 44) | public void setContent(byte[] content) {
    method getSource (line 48) | public MessageSourceEnum getSource() {
    method setSource (line 52) | public void setSource(MessageSourceEnum source) {
    method getStoredTime (line 56) | public Date getStoredTime() {
    method setStoredTime (line 60) | public void setStoredTime(Date storedTime) {
    method getTopic (line 64) | public String getTopic() {
    method setTopic (line 68) | public void setTopic(String topic) {
    method getProperties (line 72) | public Map<String, Object> getProperties() {
    method setProperties (line 76) | public void setProperties(Map<String, Object> properties) {
    method getFromClientId (line 80) | public String getFromClientId() {
    method setFromClientId (line 84) | public void setFromClientId(String fromClientId) {

FILE: jmqtt-bus/src/main/java/org/jmqtt/bus/model/DeviceSession.java
  class DeviceSession (line 13) | public class DeviceSession {
    method getClientId (line 31) | public String getClientId() {
    method setClientId (line 35) | public void setClientId(String clientId) {
    method getTransportProtocol (line 39) | public TransportProtocolEnum getTransportProtocol() {
    method setTransportProtocol (line 43) | public void setTransportProtocol(TransportProtocolEnum transportProtoc...
    method getClientIp (line 47) | public String getClientIp() {
    method setClientIp (line 51) | public void setClientIp(String clientIp) {
    method getServerIp (line 55) | public String getServerIp() {
    method setServerIp (line 59) | public void setServerIp(String serverIp) {
    method getOnline (line 63) | public DeviceOnlineStateEnum getOnline() {
    method setOnline (line 67) | public void setOnline(DeviceOnlineStateEnum online) {
    method getProperties (line 71) | public Map<String, Object> getProperties() {
    method setProperties (line 75) | public void setProperties(Map<String, Object> properties) {
    method getOnlineTime (line 79) | public Date getOnlineTime() {
    method setOnlineTime (line 83) | public void setOnlineTime(Date onlineTime) {
    method getLastOfflineTime (line 87) | public Date getLastOfflineTime() {
    method setLastOfflineTime (line 91) | public void setLastOfflineTime(Date lastOfflineTime) {

FILE: jmqtt-bus/src/main/java/org/jmqtt/bus/model/DeviceSubscription.java
  class DeviceSubscription (line 6) | public class DeviceSubscription {
    method getProperty (line 17) | public <T> T getProperty(String key){
    method getClientId (line 24) | public String getClientId() {
    method setClientId (line 28) | public void setClientId(String clientId) {
    method getTopic (line 32) | public String getTopic() {
    method setTopic (line 36) | public void setTopic(String topic) {
    method getSubscribeTime (line 40) | public Date getSubscribeTime() {
    method setSubscribeTime (line 44) | public void setSubscribeTime(Date subscribeTime) {
    method getProperties (line 48) | public Map<String, Object> getProperties() {
    method setProperties (line 52) | public void setProperties(Map<String, Object> properties) {

FILE: jmqtt-bus/src/main/java/org/jmqtt/bus/store/AbstractDBStore.java
  class AbstractDBStore (line 9) | public abstract class AbstractDBStore {
    method start (line 15) | protected void start(BrokerConfig brokerConfig) {
    method shutdown (line 19) | protected void shutdown() {
    method getMapper (line 23) | protected <T> T getMapper(SqlSession sqlSession,Class<T> clazz) {
    method operate (line 27) | protected Object operate(DBCallback dbCallback){
    method getSqlSessionWithTrans (line 31) | public SqlSession getSqlSessionWithTrans() {

FILE: jmqtt-bus/src/main/java/org/jmqtt/bus/store/DBCallback.java
  type DBCallback (line 5) | public interface DBCallback {
    method operate (line 6) | <T> T operate(SqlSession sqlSession);

FILE: jmqtt-bus/src/main/java/org/jmqtt/bus/store/DBUtils.java
  class DBUtils (line 26) | public class DBUtils {
    method DBUtils (line 39) | private DBUtils(){}
    method getInstance (line 45) | public static DBUtils getInstance(){
    method start (line 49) | public void start(BrokerConfig brokerConfig){
    method shutdown (line 98) | public void shutdown(){}
    method operate (line 101) | public static final <T> T operate(DBCallback dbCallback) {
    method getMapper (line 107) | public static final <T> T getMapper(SqlSession sqlSession,Class<T> cla...
    method getSqlSessionWithTrans (line 114) | public static final SqlSession getSqlSessionWithTrans() {

FILE: jmqtt-bus/src/main/java/org/jmqtt/bus/store/daoobject/DeviceInboxMessageDO.java
  class DeviceInboxMessageDO (line 6) | public class DeviceInboxMessageDO implements Serializable {
    method getId (line 22) | public Long getId() {
    method setId (line 26) | public void setId(Long id) {
    method getClientId (line 30) | public String getClientId() {
    method setClientId (line 34) | public void setClientId(String clientId) {
    method getMessageId (line 38) | public Long getMessageId() {
    method setMessageId (line 42) | public void setMessageId(Long messageId) {
    method getAck (line 46) | public Integer getAck() {
    method setAck (line 50) | public void setAck(Integer ack) {
    method getStoredTime (line 54) | public Date getStoredTime() {
    method setStoredTime (line 58) | public void setStoredTime(Date storedTime) {
    method getAckTime (line 62) | public Date getAckTime() {
    method setAckTime (line 66) | public void setAckTime(Date ackTime) {

FILE: jmqtt-bus/src/main/java/org/jmqtt/bus/store/daoobject/EventDO.java
  class EventDO (line 6) | public class EventDO implements Serializable {
    method getId (line 20) | public Long getId() {
    method setId (line 24) | public void setId(Long id) {
    method getContent (line 28) | public String getContent() {
    method setContent (line 32) | public void setContent(String content) {
    method getGmtCreate (line 36) | public Date getGmtCreate() {
    method setGmtCreate (line 40) | public void setGmtCreate(Date gmtCreate) {
    method getNodeIp (line 44) | public String getNodeIp() {
    method setNodeIp (line 48) | public void setNodeIp(String nodeIp) {
    method getEventCode (line 52) | public String getEventCode() {
    method setEventCode (line 56) | public void setEventCode(String eventCode) {

FILE: jmqtt-bus/src/main/java/org/jmqtt/bus/store/daoobject/MessageDO.java
  class MessageDO (line 6) | public class MessageDO implements Serializable {
    method getId (line 24) | public Long getId() {
    method setId (line 28) | public void setId(Long id) {
    method getSource (line 32) | public String getSource() {
    method setSource (line 36) | public void setSource(String source) {
    method getContent (line 40) | public byte[] getContent() {
    method setContent (line 44) | public void setContent(byte[] content) {
    method getTopic (line 48) | public String getTopic() {
    method setTopic (line 52) | public void setTopic(String topic) {
    method getFromClientId (line 56) | public String getFromClientId() {
    method setFromClientId (line 60) | public void setFromClientId(String fromClientId) {
    method getStoredTime (line 64) | public Date getStoredTime() {
    method setStoredTime (line 68) | public void setStoredTime(Date storedTime) {
    method getProperties (line 72) | public String getProperties() {
    method setProperties (line 76) | public void setProperties(String properties) {

FILE: jmqtt-bus/src/main/java/org/jmqtt/bus/store/daoobject/RetainMessageDO.java
  class RetainMessageDO (line 6) | public class RetainMessageDO implements Serializable {
    method getId (line 22) | public Long getId() {
    method setId (line 26) | public void setId(Long id) {
    method getTopic (line 30) | public String getTopic() {
    method setTopic (line 34) | public void setTopic(String topic) {
    method getContent (line 38) | public byte[] getContent() {
    method setContent (line 42) | public void setContent(byte[] content) {
    method getFromClientId (line 46) | public String getFromClientId() {
    method setFromClientId (line 50) | public void setFromClientId(String fromClientId) {
    method getStoredTime (line 54) | public Date getStoredTime() {
    method setStoredTime (line 58) | public void setStoredTime(Date storedTime) {
    method getProperties (line 62) | public String getProperties() {
    method setProperties (line 66) | public void setProperties(String properties) {

FILE: jmqtt-bus/src/main/java/org/jmqtt/bus/store/daoobject/SessionDO.java
  class SessionDO (line 7) | public class SessionDO implements Serializable {
    method getId (line 29) | public Long getId() {
    method setId (line 33) | public void setId(Long id) {
    method getClientId (line 37) | public String getClientId() {
    method setClientId (line 41) | public void setClientId(String clientId) {
    method getOnline (line 45) | public String getOnline() {
    method setOnline (line 49) | public void setOnline(String online) {
    method getTransportProtocol (line 53) | public String getTransportProtocol() {
    method setTransportProtocol (line 57) | public void setTransportProtocol(String transportProtocol) {
    method getClientIp (line 61) | public String getClientIp() {
    method setClientIp (line 65) | public void setClientIp(String clientIp) {
    method getServerIp (line 69) | public String getServerIp() {
    method setServerIp (line 73) | public void setServerIp(String serverIp) {
    method getLastOfflineTime (line 77) | public Date getLastOfflineTime() {
    method setLastOfflineTime (line 81) | public void setLastOfflineTime(Date lastOfflineTime) {
    method getOnlineTime (line 85) | public Date getOnlineTime() {
    method setOnlineTime (line 89) | public void setOnlineTime(Date onlineTime) {
    method getProperties (line 93) | public String getProperties() {
    method setProperties (line 97) | public void setProperties(String properties) {

FILE: jmqtt-bus/src/main/java/org/jmqtt/bus/store/daoobject/SubscriptionDO.java
  class SubscriptionDO (line 6) | public class SubscriptionDO implements Serializable {
    method getId (line 20) | public Long getId() {
    method setId (line 24) | public void setId(Long id) {
    method getClientId (line 28) | public String getClientId() {
    method setClientId (line 32) | public void setClientId(String clientId) {
    method getTopic (line 36) | public String getTopic() {
    method setTopic (line 40) | public void setTopic(String topic) {
    method getSubscribeTime (line 44) | public Date getSubscribeTime() {
    method setSubscribeTime (line 48) | public void setSubscribeTime(Date subscribeTime) {
    method getProperties (line 52) | public String getProperties() {
    method setProperties (line 56) | public void setProperties(String properties) {

FILE: jmqtt-bus/src/main/java/org/jmqtt/bus/store/mapper/ClientInboxMessageMapper.java
  type ClientInboxMessageMapper (line 8) | public interface ClientInboxMessageMapper {
    method addInboxMessage (line 10) | @Insert("INSERT INTO jmqtt_client_inbox(client_id,message_id,ack,store...
    method ack (line 14) | @Update("UPDATE jmqtt_client_inbox set ack = 1,ack_time = now()"
    method truncateUnAck (line 19) | @Update("DELETE FROM jmqtt_client_inbox WHERE client_id = #{clientId} ...
    method getUnAckMessages (line 22) | @Select("SELECT * FROM jmqtt_client_inbox WHERE client_id = #{clientId...

FILE: jmqtt-bus/src/main/java/org/jmqtt/bus/store/mapper/EventMapper.java
  type EventMapper (line 11) | public interface EventMapper {
    method sendEvent (line 13) | @Insert("insert into jmqtt_cluster_event (content,gmt_create,node_ip,e...
    method consumeEvent (line 19) | @Select("select id,content,gmt_create,node_ip,event_code from jmqtt_cl...
    method getMaxOffset (line 23) | @Select("SELECT max(id) FROM jmqtt_cluster_event")

FILE: jmqtt-bus/src/main/java/org/jmqtt/bus/store/mapper/MessageMapper.java
  type MessageMapper (line 11) | public interface MessageMapper {
    method storeMessage (line 13) | @Insert("INSERT INTO jmqtt_message(id,source,content,topic,from_client...
    method getMessage (line 18) | @Select("SELECT id,source,content,topic,from_client_id,stored_time,pro...
    method queryMessageByIds (line 21) | @Select("<script>SELECT id,source,content,topic,from_client_id,stored_...

FILE: jmqtt-bus/src/main/java/org/jmqtt/bus/store/mapper/RetainMessageMapper.java
  type RetainMessageMapper (line 11) | public interface RetainMessageMapper {
    method storeRetainMessage (line 13) | @Insert("INSERT INTO jmqtt_retain_message(topic,content,from_client_id...
    method getAllRetainMessage (line 19) | @Select("SELECT topic,content,from_client_id,stored_time,properties FR...
    method delRetainMessage (line 22) | @Delete("DELETE FROM jmqtt_retain_message WHERE topic = #{topic}")

FILE: jmqtt-bus/src/main/java/org/jmqtt/bus/store/mapper/SessionMapper.java
  type SessionMapper (line 10) | public interface SessionMapper {
    method getSession (line 12) | @Select("select client_id,online,transport_protocol,client_ip,server_i...
    method storeSession (line 15) | @Insert("insert into jmqtt_session(client_id,online,transport_protocol...
    method offline (line 22) | @Update("update jmqtt_session set last_offline_time = #{lastOfflineTim...

FILE: jmqtt-bus/src/main/java/org/jmqtt/bus/store/mapper/SubscriptionMapper.java
  type SubscriptionMapper (line 8) | public interface SubscriptionMapper {
    method storeSubscription (line 10) | @Insert("INSERT INTO jmqtt_subscription (client_id,topic,properties,su...
    method clearSubscription (line 15) | @Delete("DELETE FROM jmqtt_subscription WHERE client_id = #{clientId}")
    method delSubscription (line 18) | @Delete("DELETE FROM jmqtt_subscription WHERE client_id = #{clientId} ...
    method getAllSubscription (line 21) | @Select("SELECT id,client_id,topic,properties,subscribe_time FROM jmqt...

FILE: jmqtt-bus/src/main/java/org/jmqtt/bus/subscription/CNode.java
  class CNode (line 7) | public class CNode {
    method CNode (line 13) | CNode() {
    method CNode (line 19) | private CNode(Token token, List<INode> children, Set<Subscription> sub...
    method getToken (line 25) | public Token getToken() {
    method setToken (line 29) | public void setToken(Token token) {
    method anyChildrenMatch (line 33) | boolean anyChildrenMatch(Token token) {
    method allChildren (line 43) | List<INode> allChildren() {
    method childOf (line 47) | INode childOf(Token token) {
    method equalsToken (line 57) | private boolean equalsToken(Token token) {
    method hashCode (line 61) | @Override
    method copy (line 66) | CNode copy() {
    method add (line 70) | public void add(INode newINode) {
    method remove (line 74) | public void remove(INode node) {
    method addSubscription (line 78) | CNode addSubscription(Subscription newSubscription) {
    method containsOnly (line 92) | boolean containsOnly(String clientId) {
    method contains (line 101) | public boolean contains(String clientId) {
    method removeSubscriptionsFor (line 110) | void removeSubscriptionsFor(String clientId) {

FILE: jmqtt-bus/src/main/java/org/jmqtt/bus/subscription/CTrie.java
  class CTrie (line 10) | public class CTrie {
    type IVisitor (line 12) | interface IVisitor<T> {
      method visit (line 14) | void visit(CNode node, int deep);
      method getResult (line 16) | T getResult();
    type Action (line 22) | private enum Action {
    method CTrie (line 28) | public CTrie() {
    method lookup (line 34) | public Optional<CNode> lookup(Topic topic) {
    type NavigationAction (line 48) | enum NavigationAction {
    method evaluate (line 52) | private NavigationAction evaluate(Topic topic, CNode cnode) {
    method recursiveMatch (line 66) | public Set<Subscription> recursiveMatch(Topic topic) {
    method recursiveMatch (line 70) | private Set<Subscription> recursiveMatch(Topic topic, INode inode) {
    method addToTree (line 93) | public void addToTree(Subscription newSubscription) {
    method insert (line 100) | private Action insert(Topic topic, final INode inode, Subscription new...
    method insertSubscription (line 115) | private Action insertSubscription(INode inode, Subscription newSubscri...
    method createNodeAndInsertSubscription (line 125) | private Action createNodeAndInsertSubscription(Topic topic, INode inod...
    method createPathRec (line 134) | private INode createPathRec(Topic topic, Subscription newSubscription) {
    method createLeafNodes (line 147) | private INode createLeafNodes(Token token, Subscription newSubscriptio...
    method removeFromTree (line 155) | public void removeFromTree(Topic topic, String clientID) {
    method remove (line 162) | private Action remove(String clientId, Topic topic, INode inode, INode...
    method cleanTomb (line 206) | private Action cleanTomb(INode inode, INode iParent) {
    method size (line 212) | public int size() {
    method dumpTree (line 218) | public String dumpTree() {
    method dfsVisit (line 224) | private void dfsVisit(INode node, IVisitor<?> visitor, int deep) {

FILE: jmqtt-bus/src/main/java/org/jmqtt/bus/subscription/CTrieSubscriptionMatcher.java
  class CTrieSubscriptionMatcher (line 9) | public class CTrieSubscriptionMatcher implements SubscriptionMatcher {
    method CTrieSubscriptionMatcher (line 15) | public CTrieSubscriptionMatcher(){
    method match (line 19) | @Override
    method subscribe (line 26) | @Override
    method unSubscribe (line 33) | @Override
    method size (line 39) | @Override
    method dumpTree (line 44) | @Override

FILE: jmqtt-bus/src/main/java/org/jmqtt/bus/subscription/DefaultSubscriptionTreeMatcher.java
  class DefaultSubscriptionTreeMatcher (line 15) | public class DefaultSubscriptionTreeMatcher implements SubscriptionMatch...
    method DefaultSubscriptionTreeMatcher (line 25) | public DefaultSubscriptionTreeMatcher() {
    method subscribe (line 28) | @Override
    method unSubscribe (line 51) | @Override
    method recursionGetTreeNode (line 58) | private TreeNode recursionGetTreeNode(String topic, TreeNode node) {
    method match (line 79) | @Override
    method size (line 87) | @Override
    method dumpTree (line 92) | @Override
    method innerIsMatch (line 97) | private boolean innerIsMatch(String pubTopic, String subTopic) {
    method recursionMatch (line 126) | private void recursionMatch(String topic, TreeNode node,
    class Token (line 151) | class Token {
      method Token (line 154) | public Token(String token) {
      method equals (line 158) | @Override
      method hashCode (line 170) | @Override
    class TreeNode (line 176) | class TreeNode {
      method TreeNode (line 182) | public TreeNode(Token token) {
      method addSubscriber (line 186) | public void addSubscriber(Subscription subscription) {
      method addChild (line 190) | public void addChild(TreeNode treeNode) {
      method getToken (line 194) | public Token getToken() {
      method getSubscribers (line 198) | public Set<Subscription> getSubscribers() {
      method getChildren (line 202) | public List<TreeNode> getChildren() {
      method getChildNodeByToken (line 208) | public TreeNode getChildNodeByToken(Token token) {

FILE: jmqtt-bus/src/main/java/org/jmqtt/bus/subscription/DumpTreeVisitor.java
  class DumpTreeVisitor (line 6) | class DumpTreeVisitor implements CTrie.IVisitor<String> {
    method visit (line 10) | @Override
    method prettySubscriptions (line 16) | private String prettySubscriptions(CNode node) {
    method indentTabs (line 38) | private String indentTabs(int deep) {
    method getResult (line 50) | @Override

FILE: jmqtt-bus/src/main/java/org/jmqtt/bus/subscription/INode.java
  class INode (line 5) | public class INode {
    method INode (line 8) | INode(CNode mainNode) {
    method compareAndSet (line 15) | boolean compareAndSet(CNode old, CNode newNode) {
    method compareAndSet (line 19) | boolean compareAndSet(CNode old, TNode newNode) {
    method mainNode (line 23) | CNode mainNode() {
    method isTombed (line 27) | boolean isTombed() {

FILE: jmqtt-bus/src/main/java/org/jmqtt/bus/subscription/SubscriptionCounterVisitor.java
  class SubscriptionCounterVisitor (line 5) | class SubscriptionCounterVisitor implements CTrie.IVisitor<Integer> {
    method visit (line 9) | @Override
    method getResult (line 14) | @Override

FILE: jmqtt-bus/src/main/java/org/jmqtt/bus/subscription/SubscriptionMatcher.java
  type SubscriptionMatcher (line 14) | public interface SubscriptionMatcher {
    method subscribe (line 21) | boolean subscribe(Subscription subscription);
    method unSubscribe (line 23) | boolean unSubscribe(String topic,String clientId);
    method match (line 28) | Set<Subscription> match(String topic);
    method size (line 31) | int size();
    method dumpTree (line 33) | String dumpTree();

FILE: jmqtt-bus/src/main/java/org/jmqtt/bus/subscription/TNode.java
  class TNode (line 20) | class TNode extends CNode {
    method getToken (line 22) | @Override
    method setToken (line 27) | @Override
    method childOf (line 32) | @Override
    method copy (line 37) | @Override
    method add (line 42) | @Override
    method addSubscription (line 47) | @Override
    method containsOnly (line 52) | @Override
    method contains (line 57) | @Override
    method removeSubscriptionsFor (line 62) | @Override
    method anyChildrenMatch (line 67) | @Override

FILE: jmqtt-bus/src/main/java/org/jmqtt/bus/subscription/Token.java
  class Token (line 4) | public class Token {
    method Token (line 11) | public Token(String s) {
    method name (line 15) | protected String name() {
    method match (line 19) | protected boolean match(Token t) {
    method hashCode (line 31) | @Override
    method equals (line 38) | @Override
    method toString (line 53) | @Override

FILE: jmqtt-bus/src/main/java/org/jmqtt/bus/subscription/Topic.java
  class Topic (line 15) | public class Topic implements Serializable , Comparable<Topic> {
    method asTopic (line 33) | public static Topic asTopic(String s) {
    method Topic (line 37) | public Topic(String topic) {
    method Topic (line 41) | Topic(List<Token> tokens) {
    method getTokens (line 48) | public List<Token> getTokens() {
    method parseTopic (line 62) | private List<Token> parseTopic(String topic) throws ParseException {
    method headToken (line 112) | public Token headToken() {
    method isEmpty (line 120) | public boolean isEmpty() {
    method exceptHeadToken (line 125) | public Topic exceptHeadToken() {
    method isValid (line 135) | public boolean isValid() {
    method match (line 142) | public boolean match(Topic subscriptionTopic) {
    method toString (line 165) | @Override
    method equals (line 170) | @Override
    method hashCode (line 183) | @Override
    method compareTo (line 188) | @Override

FILE: jmqtt-bus/src/main/java/org/jmqtt/bus/subscription/model/Subscription.java
  class Subscription (line 9) | public class Subscription {
    method Subscription (line 14) | public Subscription(String clientId,String topic){
    method Subscription (line 19) | public Subscription(String clientId,String topic,Map<String,Object> pr...
    method Subscription (line 25) | public Subscription(Subscription origin){
    method getProperty (line 31) | public <T> T getProperty(String key){
    method getClientId (line 39) | public String getClientId() {
    method setClientId (line 43) | public void setClientId(String clientId) {
    method getTopic (line 47) | public String getTopic() {
    method setTopic (line 51) | public void setTopic(String topic) {
    method getProperties (line 55) | public Map<String, Object> getProperties() {
    method setProperties (line 59) | public void setProperties(Map<String, Object> properties) {
    method equals (line 63) | @Override
    method hashCode (line 72) | @Override
    method toString (line 77) | @Override

FILE: jmqtt-bus/src/test/java/org/jmqtt/AppTest.java
  class AppTest (line 10) | public class AppTest
    method shouldAnswerWithTrue (line 15) | @Test

FILE: jmqtt-bus/src/test/java/org/jmqtt/bus/subscription/CTrieSubscriptionMatcherMatchingTest.java
  class CTrieSubscriptionMatcherMatchingTest (line 30) | class CTrieSubscriptionMatcherMatchingTest {
    method setUp (line 34) | @BeforeEach
    method testMatchSimple (line 39) | @Test
    method testMatchSimpleMulti (line 53) | @Test
    method testMatchingDeepMulti_one_layer (line 64) | @Test
    method testMatchingDeepMulti_two_layer (line 79) | @Test
    method testMatchSimpleSingle (line 88) | @Test
    method testMatchManySingle (line 99) | @Test
    method testMatchSlashSingle (line 108) | @Test
    method testMatchManyDeepSingle (line 120) | @Test
    method testMatchSimpleMulti_allTheTree (line 132) | @Test
    method rogerLightTopicMatches (line 141) | @Test
    method assertMatch (line 165) | private void assertMatch(String s, String t) {
    method assertNotMatch (line 174) | private void assertNotMatch(String subscription, String topic) {
    method testOverlappingSubscriptions (line 183) | @Test
    method removeSubscription_withDifferentClients_subscribedSameTopic (line 198) | @Test
    method removeSubscription_sameClients_subscribedSameTopic (line 214) | @Test
    method duplicatedSubscriptionsWithDifferentQos (line 230) | @Test

FILE: jmqtt-bus/src/test/java/org/jmqtt/bus/subscription/CTrieTest.java
  class CTrieTest (line 28) | public class CTrieTest {
    method setUp (line 32) | @BeforeEach
    method testAddOnSecondLayerWithEmptyTokenOnEmptyTree (line 37) | @Test
    method testAddFirstLayerNodeOnEmptyTree (line 58) | @Test
    method testLookup (line 69) | @Test
    method testAddNewSubscriptionOnExistingNode (line 81) | @Test
    method testAddNewDeepNodes (line 97) | @Test
    method clientSubOnTopic (line 112) | static Subscription clientSubOnTopic(String clientID, String topicName) {
    method givenTreeWithSomeNodeWhenRemoveContainedSubscriptionThenNodeIsUpdated (line 116) | @Test
    method givenTreeWithSomeNodeUnsubscribeAndResubscribeCleanTomb (line 128) | @Test
    method givenTreeWithSomeNodeWhenRemoveMultipleTimes (line 137) | @Test
    method givenTreeWithSomeDeepNodeWhenRemoveMultipleTimes (line 152) | @Test
    method givenTreeWithSomeNodeHierarchyWhenRemoveContainedSubscriptionThenNodeIsUpdated (line 166) | @Test
    method givenTreeWithSomeNodeHierarchWhenRemoveContainedSubscriptionSmallerThenNodeIsNotUpdated (line 182) | @Test
    method givenTreeWithDeepNodeWhenRemoveContainedSubscriptionThenNodeIsUpdated (line 201) | @Test
    method testMatchSubscriptionNoWildcards (line 212) | @Test
    method testRemovalInnerTopicOffRootSameClient (line 224) | @Test
    method testRemovalInnerTopicOffRootDiffClient (line 250) | @Test
    method testRemovalOuterTopicOffRootDiffClient (line 276) | @Test

FILE: jmqtt-bus/src/test/java/org/jmqtt/bus/subscription/TopicTest.java
  class TopicTest (line 24) | public class TopicTest {
    method testParseTopic (line 26) | @Test
    method testParseTopicMultiValid (line 35) | @Test
    method testValidationProcess (line 42) | @Test
    method testParseTopicSingleValid (line 59) | @Test
    method testMatchTopics_simple (line 68) | @Test
    method testMatchTopics_multi (line 74) | @Test
    method testMatchTopics_single (line 82) | @Test
    method rogerLightMatchTopics (line 96) | @Test
    method exceptHeadToken (line 119) | @Test
    method assertThat (line 125) | public static TopicAssert assertThat(Topic topic) {
    class TopicAssert (line 129) | static class TopicAssert extends AbstractAssert<TopicAssert, Topic> {
      method TopicAssert (line 131) | TopicAssert(Topic actual) {
      method matches (line 135) | public TopicAssert matches(String topic) {
      method doesNotMatch (line 141) | public TopicAssert doesNotMatch(String topic) {
      method containsToken (line 147) | public TopicAssert containsToken(Object... tokens) {
      method asArray (line 153) | private Token[] asArray(Object... l) {
      method isValid (line 167) | public TopicAssert isValid() {
      method isInValid (line 173) | public TopicAssert isInValid() {

FILE: jmqtt-example/src/main/java/org/jmqtt/java/Consumer.java
  class Consumer (line 6) | public class Consumer {
    method main (line 11) | public static void main(String[] args) throws MqttException {
    method getMqttClient (line 35) | private static MqttClient getMqttClient() {

FILE: jmqtt-example/src/main/java/org/jmqtt/java/Producer.java
  class Producer (line 9) | public class Producer {
    method main (line 16) | public static void main(String[] args) throws MqttException, Interrupt...
    method getMqttMessage (line 25) | private static MqttMessage getMqttMessage() {
    method getMqttClient (line 31) | private static MqttClient getMqttClient() {

FILE: jmqtt-example/src/main/java/org/jmqtt/websocket/paho-mqtt-min.js
  function y (line 16) | function y(a,b,c){b[c++]=a>>8;b[c++]=a%256;return c}
  function r (line 16) | function r(a,b,c,h){h=y(b,c,h);F(a,c,h);return h+b}
  function m (line 16) | function m(a){for(var b=0,c=0;c<a.length;c++){var h=a.charCodeAt(c);2047...
  function F (line 16) | function F(a,b,c){for(var h=0;h<a.length;h++){var e=a.charCodeAt(h);if(5...
  function G (line 17) | function G(a,b,c){for(var h="",e,d=b;d<b+c;){e=a[d++];if(!(128>e)){var p...
  method host (line 68) | get host(){return this._getHost()}
  method host (line 68) | set host(a){this._setHost(a)}
  method port (line 68) | get port(){return this._getPort()}
  method port (line 68) | set port(a){this._setPort(a)}
  method path (line 68) | get path(){return this._getPath()}
  method path (line 68) | set path(a){this._setPath(a)}
  method clientId (line 68) | get clientId(){return this._getClientId()}
  method clientId (line 68) | set clientId(a){this._setClientId(a)}
  method onConnectionLost (line 68) | get onConnectionLost(){return this._getOnConnectionLost()}
  method onConnectionLost (line 68) | set onConnectionLost(a){this._setOnConnectionLost(a)}
  method onMessageDelivered (line 68) | get onMessageDelivered(){return this._getOnMessageDelivered()}
  method onMessageDelivered (line 68) | set onMessageDelivered(a){this._setOnMessageDelivered(a)}
  method onMessageArrived (line 69) | get onMessageArrived(){return this._getOnMessageArrived()}
  method onMessageArrived (line 69) | set onMessageArrived(a){this._setOnMessageArrived(a)}
  method trace (line 69) | get trace(){return this._getTrace()}
  method trace (line 69) | set trace(a){this._setTrace(a)}
  method payloadString (line 71) | get payloadString(){return this._getPayloadString()}
  method payloadBytes (line 71) | get payloadBytes(){return this._getPayloadBytes()}
  method destinationName (line 71) | get destinationName(){return this._getDestinationName()}
  method destinationName (line 71) | set destinationName(a){this._setDestinationName(a)}
  method qos (line 71) | get qos(){return this._getQos()}
  method qos (line 72) | set qos(a){this._setQos(a)}
  method retained (line 72) | get retained(){return this._getRetained()}
  method retained (line 72) | set retained(a){this._setRetained(a)}
  method duplicate (line 72) | get duplicate(){return this._getDuplicate()}
  method duplicate (line 72) | set duplicate(a){this._setDuplicate(a)}

FILE: jmqtt-example/src/main/java/org/jmqtt/websocket/paho-mqtt.js
  function decodeMessage (line 444) | function decodeMessage(input,pos) {
  function writeUint16 (line 524) | function writeUint16(input, buffer, offset) {
  function writeString (line 530) | function writeString(input, utf8Length, buffer, offset) {
  function readUint16 (line 536) | function readUint16(buffer, offset) {
  function encodeMBI (line 544) | function encodeMBI(number) {
  function UTF8Length (line 564) | function UTF8Length(input) {
  function stringToUTF8 (line 591) | function stringToUTF8(input, output, start) {
  function parseUTF8 (line 625) | function parseUTF8(input, offset, length) {
  method host (line 1992) | get host() { return this._getHost(); }
  method host (line 1993) | set host(newHost) { this._setHost(newHost); }
  method port (line 1995) | get port() { return this._getPort(); }
  method port (line 1996) | set port(newPort) { this._setPort(newPort); }
  method path (line 1998) | get path() { return this._getPath(); }
  method path (line 1999) | set path(newPath) { this._setPath(newPath); }
  method clientId (line 2001) | get clientId() { return this._getClientId(); }
  method clientId (line 2002) | set clientId(newClientId) { this._setClientId(newClientId); }
  method onConnectionLost (line 2004) | get onConnectionLost() { return this._getOnConnectionLost(); }
  method onConnectionLost (line 2005) | set onConnectionLost(newOnConnectionLost) { this._setOnConnectionLost(ne...
  method onMessageDelivered (line 2007) | get onMessageDelivered() { return this._getOnMessageDelivered(); }
  method onMessageDelivered (line 2008) | set onMessageDelivered(newOnMessageDelivered) { this._setOnMessageDelive...
  method onMessageArrived (line 2010) | get onMessageArrived() { return this._getOnMessageArrived(); }
  method onMessageArrived (line 2011) | set onMessageArrived(newOnMessageArrived) { this._setOnMessageArrived(ne...
  method trace (line 2013) | get trace() { return this._getTrace(); }
  method trace (line 2014) | set trace(newTraceFunction) { this._setTrace(newTraceFunction); }
  method payloadString (line 2122) | get payloadString() { return this._getPayloadString(); }
  method payloadBytes (line 2123) | get payloadBytes() { return this._getPayloadBytes(); }
  method destinationName (line 2125) | get destinationName() { return this._getDestinationName(); }
  method destinationName (line 2126) | set destinationName(newDestinationName) { this._setDestinationName(newDe...
  method qos (line 2128) | get qos() { return this._getQos(); }
  method qos (line 2129) | set qos(newQos) { this._setQos(newQos); }
  method retained (line 2131) | get retained() { return this._getRetained(); }
  method retained (line 2132) | set retained(newRetained) { this._setRetained(newRetained); }
  method duplicate (line 2134) | get duplicate() { return this._getDuplicate(); }
  method duplicate (line 2135) | set duplicate(newDuplicate) { this._setDuplicate(newDuplicate); }

FILE: jmqtt-mqtt/src/main/java/org/jmqtt/mqtt/ConnectManager.java
  class ConnectManager (line 10) | public class ConnectManager {
    method ConnectManager (line 16) | private ConnectManager(){}
    method getInstance (line 18) | public static  ConnectManager getInstance(){
    method getAllConnections (line 22) | public Collection<MQTTConnection> getAllConnections(){
    method getClient (line 26) | public MQTTConnection getClient(String clientId){
    method putClient (line 30) | public MQTTConnection putClient(String clientId,MQTTConnection clientS...
    method containClient (line 34) | public boolean containClient(String clientId){
    method removeClient (line 38) | public MQTTConnection removeClient(String clientId){

FILE: jmqtt-mqtt/src/main/java/org/jmqtt/mqtt/MQTTConnection.java
  class MQTTConnection (line 40) | public class MQTTConnection {
    method MQTTConnection (line 57) | public MQTTConnection(Channel channel, Map<Integer, Pair<RequestProces...
    method processProtocol (line 72) | public void processProtocol(ChannelHandlerContext ctx, MqttMessage mqt...
    method processPubComp (line 82) | public void processPubComp(MqttMessage mqttMessage) {
    method processPubRec (line 92) | public void processPubRec(MqttMessage mqttMessage){
    method processPubAck (line 108) | public void processPubAck(MqttMessage mqttMessage){
    method processUnSubscribe (line 119) | public void processUnSubscribe(MqttUnsubscribeMessage mqttUnsubscribeM...
    method processSubscribe (line 131) | public void processSubscribe(MqttSubscribeMessage subscribeMessage) {
    method publishMessage (line 190) | public void publishMessage(MqttPublishMessage mqttPublishMessage){
    method validTopics (line 197) | private List<MqttTopic> validTopics(List<MqttTopicSubscription> topics) {
    method processPublishMessage (line 211) | public void processPublishMessage(MqttPublishMessage mqttPublishMessag...
    method processQos2 (line 229) | private void processQos2(MqttPublishMessage mqttPublishMessage) {
    method processQos1 (line 238) | private void processQos1(MqttPublishMessage mqttPublishMessage) {
    method processPubRelMessage (line 246) | public void processPubRelMessage(MqttMessage mqttMessage) {
    method dispatcherMessage (line 259) | private void dispatcherMessage(DeviceMessage deviceMessage) {
    method dispatcherMessage (line 275) | private void dispatcherMessage(MqttPublishMessage mqttPublishMessage) {
    method handleConnectionLost (line 287) | public void handleConnectionLost() {
    method createOrReopenSession (line 302) | public boolean createOrReopenSession(MqttConnectMessage mqttConnectMes...
    method storeSession (line 371) | public void storeSession() {
    method reSendMessage2Client (line 386) | public void reSendMessage2Client() {
    method keepAlive (line 406) | public boolean keepAlive(int heatbeatSec) {
    method login (line 415) | public boolean login(String clientId, String username, byte[] password) {
    method onBlackList (line 419) | public boolean onBlackList(String remoteAddr, String clientId) {
    method clientIdVerify (line 423) | public boolean clientIdVerify(String clientId) {
    method abortConnection (line 427) | public void abortConnection(MqttConnectReturnCode returnCode) {
    method processDisconnect (line 435) | public void processDisconnect() {
    method clearSession (line 442) | private void clearSession() {
    method reloadSubscriptions (line 447) | private void reloadSubscriptions() {
    method offline (line 456) | private void offline() {
    method getClientId (line 461) | public String getClientId() {
    method getChannel (line 465) | public Channel getChannel() {
    method getProcessorTable (line 469) | public Map<Integer, Pair<RequestProcessor, ExecutorService>> getProces...
    method getBrokerConfig (line 473) | public BrokerConfig getBrokerConfig() {
    method getBindedSession (line 477) | public MqttSession getBindedSession() {
    method getAuthenticator (line 481) | public Authenticator getAuthenticator() {
    method getDeviceSessionManager (line 485) | public DeviceSessionManager getDeviceSessionManager() {
    method getDeviceSubscriptionManager (line 489) | public DeviceSubscriptionManager getDeviceSubscriptionManager() {
    method getDeviceMessageManager (line 493) | public DeviceMessageManager getDeviceMessageManager() {
    method getRetainMessageHandler (line 497) | public RetainMessageHandler getRetainMessageHandler() {
    method getClusterEventManager (line 501) | public ClusterEventManager getClusterEventManager() {

FILE: jmqtt-mqtt/src/main/java/org/jmqtt/mqtt/MQTTConnectionFactory.java
  class MQTTConnectionFactory (line 26) | public class MQTTConnectionFactory {
    method MQTTConnectionFactory (line 48) | public MQTTConnectionFactory(BrokerConfig brokerConfig, BusController ...
    method registerProcessor (line 114) | public void registerProcessor(int mqttMessageType, RequestProcessor re...
    method create (line 118) | public MQTTConnection create(Channel channel){
    method shutdown (line 124) | public void shutdown(){

FILE: jmqtt-mqtt/src/main/java/org/jmqtt/mqtt/MQTTServer.java
  class MQTTServer (line 20) | public class MQTTServer {
    method MQTTServer (line 30) | public MQTTServer(BusController busController, BrokerConfig brokerConf...
    method start (line 38) | public void start(){
    method shutdown (line 45) | public void shutdown(){

FILE: jmqtt-mqtt/src/main/java/org/jmqtt/mqtt/codec/ByteBuf2WebSocketEncoder.java
  class ByteBuf2WebSocketEncoder (line 10) | public class ByteBuf2WebSocketEncoder extends MessageToMessageEncoder<By...
    method encode (line 11) | @Override

FILE: jmqtt-mqtt/src/main/java/org/jmqtt/mqtt/codec/WebSocket2ByteBufDecoder.java
  class WebSocket2ByteBufDecoder (line 10) | public class WebSocket2ByteBufDecoder extends MessageToMessageDecoder<Bi...
    method decode (line 12) | @Override

FILE: jmqtt-mqtt/src/main/java/org/jmqtt/mqtt/event/MqttEventListener.java
  class MqttEventListener (line 25) | public class MqttEventListener implements GatewayListener {
    method MqttEventListener (line 33) | public MqttEventListener(DeviceMessageManager deviceMessageManager) {
    method consume (line 44) | @Override
    method sendMessage2Client (line 62) | private void sendMessage2Client(ClusterEvent clusterEvent) {
    method clearCurrentNodeSession (line 96) | private void clearCurrentNodeSession(ClusterEvent clusterEvent){

FILE: jmqtt-mqtt/src/main/java/org/jmqtt/mqtt/model/MqttTopic.java
  class MqttTopic (line 3) | public class MqttTopic {
    method MqttTopic (line 8) | public MqttTopic(String topicName, int qos) {
    method getTopicName (line 13) | public String getTopicName() {
    method setTopicName (line 17) | public void setTopicName(String topicName) {
    method getQos (line 21) | public int getQos() {
    method setQos (line 25) | public void setQos(int qos) {

FILE: jmqtt-mqtt/src/main/java/org/jmqtt/mqtt/netty/MqttNettyUtils.java
  class MqttNettyUtils (line 12) | public class MqttNettyUtils {
    method getAttribute (line 25) | public static Object getAttribute(ChannelHandlerContext ctx, Attribute...
    method keepAlive (line 30) | public static void keepAlive(Channel channel, int keepAlive) {
    method cleanSession (line 34) | public static void cleanSession(Channel channel, boolean cleanSession) {
    method cleanSession (line 38) | public static boolean cleanSession(Channel channel) {
    method clientID (line 42) | public static void clientID(Channel channel, String clientID) {
    method clientID (line 46) | public static String clientID(Channel channel) {
    method mqttConnection (line 51) | public static void mqttConnection(Channel channel, MQTTConnection mqtt...
    method mqttConnection (line 55) | public static MQTTConnection mqttConnection(Channel channel) {
    method userName (line 59) | public static void userName(Channel channel, String username) {
    method userName (line 63) | public static String userName(Channel channel) {
    method validateMessage (line 67) | public static MqttMessage validateMessage(Object message) throws IOExc...
    method MqttNettyUtils (line 78) | private MqttNettyUtils() {

FILE: jmqtt-mqtt/src/main/java/org/jmqtt/mqtt/netty/MqttRemotingServer.java
  class MqttRemotingServer (line 33) | public class MqttRemotingServer implements RemotingService {
    method MqttRemotingServer (line 45) | public MqttRemotingServer(BrokerConfig brokerConfig, NettyConfig netty...
    method start (line 66) | @Override
    method startWebsocketServer (line 87) | private void startWebsocketServer(boolean useSsl, Integer port) {
    method startTcpServer (line 134) | private void startTcpServer(boolean useSsl, Integer port) {
    method shutdown (line 176) | @Override

FILE: jmqtt-mqtt/src/main/java/org/jmqtt/mqtt/netty/NettyMqttHandler.java
  class NettyMqttHandler (line 17) | public class NettyMqttHandler extends ChannelDuplexHandler {
    method NettyMqttHandler (line 23) | NettyMqttHandler(MQTTConnectionFactory connectionFactory) {
    method channelRead (line 27) | @Override
    method channelActive (line 44) | @Override
    method channelInactive (line 50) | @Override
    method exceptionCaught (line 56) | @Override
    method channelWritabilityChanged (line 63) | @Override
    method userEventTriggered (line 68) | @Override

FILE: jmqtt-mqtt/src/main/java/org/jmqtt/mqtt/netty/NettySslHandler.java
  class NettySslHandler (line 17) | public class NettySslHandler {
    method getSslHandler (line 20) | public static ChannelHandler getSslHandler(SocketChannel channel, bool...
    method createSSLContext (line 35) | private static SslContext createSSLContext(boolean useClientCA, String...

FILE: jmqtt-mqtt/src/main/java/org/jmqtt/mqtt/protocol/RequestProcessor.java
  type RequestProcessor (line 6) | public interface RequestProcessor {
    method processRequest (line 12) | void processRequest(ChannelHandlerContext ctx, MqttMessage mqttMessage);

FILE: jmqtt-mqtt/src/main/java/org/jmqtt/mqtt/protocol/impl/ConnectProcessor.java
  class ConnectProcessor (line 22) | public class ConnectProcessor implements RequestProcessor {
    method processRequest (line 26) | @Override
    method versionValid (line 95) | private boolean versionValid(int mqttVersion) {

FILE: jmqtt-mqtt/src/main/java/org/jmqtt/mqtt/protocol/impl/DisconnectProcessor.java
  class DisconnectProcessor (line 16) | public class DisconnectProcessor implements RequestProcessor {
    method processRequest (line 20) | @Override

FILE: jmqtt-mqtt/src/main/java/org/jmqtt/mqtt/protocol/impl/PingProcessor.java
  class PingProcessor (line 11) | public class PingProcessor implements RequestProcessor {
    method processRequest (line 13) | @Override

FILE: jmqtt-mqtt/src/main/java/org/jmqtt/mqtt/protocol/impl/PubAckProcessor.java
  class PubAckProcessor (line 14) | public class PubAckProcessor implements RequestProcessor {
    method processRequest (line 18) | @Override

FILE: jmqtt-mqtt/src/main/java/org/jmqtt/mqtt/protocol/impl/PubCompProcessor.java
  class PubCompProcessor (line 12) | public class PubCompProcessor implements RequestProcessor {
    method processRequest (line 15) | @Override

FILE: jmqtt-mqtt/src/main/java/org/jmqtt/mqtt/protocol/impl/PubRecProcessor.java
  class PubRecProcessor (line 13) | public class PubRecProcessor implements RequestProcessor {
    method processRequest (line 16) | @Override

FILE: jmqtt-mqtt/src/main/java/org/jmqtt/mqtt/protocol/impl/PubRelProcessor.java
  class PubRelProcessor (line 18) | public class PubRelProcessor implements RequestProcessor {
    method processRequest (line 22) | @Override

FILE: jmqtt-mqtt/src/main/java/org/jmqtt/mqtt/protocol/impl/PublishProcessor.java
  class PublishProcessor (line 17) | public class PublishProcessor implements RequestProcessor {
    method processRequest (line 21) | @Override

FILE: jmqtt-mqtt/src/main/java/org/jmqtt/mqtt/protocol/impl/SubscribeProcessor.java
  class SubscribeProcessor (line 14) | public class SubscribeProcessor implements RequestProcessor {
    method processRequest (line 16) | @Override

FILE: jmqtt-mqtt/src/main/java/org/jmqtt/mqtt/protocol/impl/UnSubscribeProcessor.java
  class UnSubscribeProcessor (line 13) | public class UnSubscribeProcessor implements RequestProcessor {
    method processRequest (line 15) | @Override

FILE: jmqtt-mqtt/src/main/java/org/jmqtt/mqtt/retain/RetainMessageHandler.java
  type RetainMessageHandler (line 7) | public interface RetainMessageHandler {
    method getAllRetatinMessage (line 9) | List<DeviceMessage> getAllRetatinMessage();
    method clearRetainMessage (line 11) | void clearRetainMessage(String topic);
    method storeRetainMessage (line 13) | void storeRetainMessage(DeviceMessage deviceMessage);

FILE: jmqtt-mqtt/src/main/java/org/jmqtt/mqtt/retain/impl/RetainMessageHandlerImpl.java
  class RetainMessageHandlerImpl (line 17) | public class RetainMessageHandlerImpl implements RetainMessageHandler {
    method getAllRetatinMessage (line 21) | @Override
    method clearRetainMessage (line 48) | @Override
    method storeRetainMessage (line 58) | @Override
    method convert (line 72) | private RetainMessageDO convert(DeviceMessage deviceMessage) {

FILE: jmqtt-mqtt/src/main/java/org/jmqtt/mqtt/session/MqttSession.java
  class MqttSession (line 25) | public class MqttSession {
    method addSubscription (line 46) | public void addSubscription(DeviceSubscription deviceSubscription) {
    method removeSubscription (line 50) | public void removeSubscription(String topic) {
    method receivePubRec (line 54) | public void receivePubRec(int packetId){
    method releaseQos2SecFlow (line 58) | public boolean releaseQos2SecFlow(int packetId){
    method releaseOutboundFlowMessage (line 62) | public DeviceMessage releaseOutboundFlowMessage(int packetId){
    method sendMessage (line 70) | public void sendMessage(DeviceMessage deviceMessage) {
    method sendQos0Message (line 89) | private void sendQos0Message(DeviceMessage deviceMessage) {
    method sendQos1Or2Message (line 94) | private void sendQos1Or2Message(DeviceMessage deviceMessage) {
    method receivedPublishQos2 (line 104) | public void receivedPublishQos2(int originPacketId,DeviceMessage devic...
    method receivedPubRelQos2 (line 108) | public DeviceMessage receivedPubRelQos2(int packgetId) {
    method clearWill (line 112) | public void clearWill(){
    method hasWill (line 116) | public boolean hasWill(){
    method nextPacketId (line 120) | public int nextPacketId() {
    method getClientId (line 125) | public String getClientId() {
    method setClientId (line 129) | public void setClientId(String clientId) {
    method isCleanSession (line 133) | public boolean isCleanSession() {
    method setCleanSession (line 137) | public void setCleanSession(boolean cleanSession) {
    method getMqttVersion (line 141) | public int getMqttVersion() {
    method setMqttVersion (line 145) | public void setMqttVersion(int mqttVersion) {
    method getWill (line 149) | public DeviceMessage getWill() {
    method setWill (line 153) | public void setWill(DeviceMessage will) {
    method getMqttConnection (line 158) | public MQTTConnection getMqttConnection() {
    method setMqttConnection (line 162) | public void setMqttConnection(MQTTConnection mqttConnection) {
    method getClientIp (line 166) | public String getClientIp() {
    method setClientIp (line 170) | public void setClientIp(String clientIp) {
    method getServerIp (line 174) | public String getServerIp() {
    method setServerIp (line 178) | public void setServerIp(String serverIp) {
    method getSubscriptions (line 182) | public Map<String, DeviceSubscription> getSubscriptions() {
    method setSubscriptions (line 186) | public void setSubscriptions(Map<String, DeviceSubscription> subscript...

FILE: jmqtt-mqtt/src/main/java/org/jmqtt/mqtt/utils/MqttMessageUtil.java
  class MqttMessageUtil (line 13) | public class MqttMessageUtil {
    method readBytesFromByteBuf (line 15) | public static byte[] readBytesFromByteBuf(ByteBuf byteBuf){
    method getUnSubAckMessage (line 22) | public static MqttUnsubAckMessage getUnSubAckMessage(int messageId){
    method getMessageId (line 28) | public static int getMessageId(MqttMessage mqttMessage){
    method getMinQos (line 33) | public static int getMinQos(int qos1,int qos2){
    method getPubRelMessage (line 40) | public static MqttMessage getPubRelMessage(int messageId){
    method getPubMessage (line 46) | public static MqttPublishMessage getPubMessage(DeviceMessage message,i...
    method getSubAckMessage (line 64) | public static MqttMessage getSubAckMessage(int messageId, List<Integer...
    method getPingRespMessage (line 71) | public static MqttMessage getPingRespMessage(){
    method getPubComMessage (line 77) | public static MqttMessage getPubComMessage(int messageId){
    method getPubRecMessage (line 83) | public static MqttMessage getPubRecMessage(int messageId){
    method getPubRecMessage (line 89) | public static MqttMessage getPubRecMessage(int messageId,boolean isDup){
    method getPubAckMessage (line 95) | public static MqttPubAckMessage getPubAckMessage(int messageId){
    method getConnectAckMessage (line 101) | public static MqttConnAckMessage getConnectAckMessage(MqttConnectRetur...

FILE: jmqtt-mqtt/src/main/java/org/jmqtt/mqtt/utils/MqttMsgHeader.java
  class MqttMsgHeader (line 11) | public class MqttMsgHeader {
    method buildDeviceMessage (line 21) | public static final DeviceMessage buildDeviceMessage(boolean retain,in...
    method buildDeviceMessage (line 36) | public static final DeviceMessage buildDeviceMessage(MqttPublishMessag...

FILE: jmqtt-support/src/main/java/org/jmqtt/support/config/BrokerConfig.java
  class BrokerConfig (line 5) | public class BrokerConfig {
    method getPollThreadNum (line 29) | public int getPollThreadNum() {
    method setPollThreadNum (line 33) | public void setPollThreadNum(int pollThreadNum) {
    method getJmqttHome (line 37) | public String getJmqttHome() {
    method setJmqttHome (line 41) | public void setJmqttHome(String jmqttHome) {
    method getVersion (line 45) | public String getVersion() {
    method setVersion (line 49) | public void setVersion(String version) {
    method getAnonymousEnable (line 53) | public boolean getAnonymousEnable() {
    method setAnonymousEnable (line 57) | public void setAnonymousEnable(boolean anonymousEnable) {
    method getDriver (line 61) | public String getDriver() {
    method setDriver (line 65) | public void setDriver(String driver) {
    method getUrl (line 69) | public String getUrl() {
    method setUrl (line 73) | public void setUrl(String url) {
    method getUsername (line 77) | public String getUsername() {
    method setUsername (line 81) | public void setUsername(String username) {
    method getPassword (line 85) | public String getPassword() {
    method setPassword (line 89) | public void setPassword(String password) {
    method isAnonymousEnable (line 93) | public boolean isAnonymousEnable() {
    method getMaxPollEventNum (line 97) | public int getMaxPollEventNum() {
    method setMaxPollEventNum (line 101) | public void setMaxPollEventNum(int maxPollEventNum) {
    method getPollWaitInterval (line 105) | public int getPollWaitInterval() {
    method setPollWaitInterval (line 109) | public void setPollWaitInterval(int pollWaitInterval) {
    method getLogLevel (line 113) | public String getLogLevel() {
    method setLogLevel (line 117) | public void setLogLevel(String logLevel) {

FILE: jmqtt-support/src/main/java/org/jmqtt/support/config/NettyConfig.java
  class NettyConfig (line 3) | public class NettyConfig {
    method getTcpBackLog (line 56) | public int getTcpBackLog() {
    method setTcpBackLog (line 60) | public void setTcpBackLog(int tcpBackLog) {
    method getTcpNoDelay (line 64) | public boolean getTcpNoDelay() {
    method setTcpNoDelay (line 68) | public void setTcpNoDelay(boolean tcpNoDelay) {
    method getTcpReuseAddr (line 72) | public boolean getTcpReuseAddr() {
    method setTcpReuseAddr (line 76) | public void setTcpReuseAddr(boolean tcpReuseAddr) {
    method getTcpKeepAlive (line 80) | public boolean getTcpKeepAlive() {
    method setTcpKeepAlive (line 84) | public void setTcpKeepAlive(boolean tcpKeepAlive) {
    method getTcpSndBuf (line 88) | public int getTcpSndBuf() {
    method setTcpSndBuf (line 92) | public void setTcpSndBuf(int tcpSndBuf) {
    method getTcpRcvBuf (line 96) | public int getTcpRcvBuf() {
    method setTcpRcvBuf (line 100) | public void setTcpRcvBuf(int tcpRcvBuf) {
    method getTcpPort (line 104) | public int getTcpPort() {
    method setTcpPort (line 108) | public void setTcpPort(int tcpPort) {
    method getMaxMsgSize (line 112) | public int getMaxMsgSize() {
    method setMaxMsgSize (line 116) | public void setMaxMsgSize(int maxMsgSize) {
    method getUseEpoll (line 120) | public boolean getUseEpoll() {
    method setUseEpoll (line 124) | public void setUseEpoll(boolean useEpoll) {
    method getPooledByteBufAllocatorEnable (line 128) | public boolean getPooledByteBufAllocatorEnable() {
    method setPooledByteBufAllocatorEnable (line 132) | public void setPooledByteBufAllocatorEnable(boolean pooledByteBufAlloc...
    method getStartWebsocket (line 136) | public boolean getStartWebsocket() {
    method setStartWebsocket (line 140) | public void setStartWebsocket(boolean startWebsocket) {
    method getWebsocketPort (line 144) | public int getWebsocketPort() {
    method setWebsocketPort (line 148) | public void setWebsocketPort(int websocketPort) {
    method getStartTcp (line 152) | public boolean getStartTcp() {
    method setStartTcp (line 156) | public void setStartTcp(boolean startTcp) {
    method getStartSslTcp (line 161) | public boolean getStartSslTcp() {
    method setStartSslTcp (line 165) | public void setStartSslTcp(boolean startSslTcp) {
    method getSslTcpPort (line 170) | public int getSslTcpPort() {
    method setSslTcpPort (line 174) | public void setSslTcpPort(int sslTcpPort) {
    method getStartSslWebsocket (line 178) | public boolean getStartSslWebsocket() {
    method setStartSslWebsocket (line 182) | public void setStartSslWebsocket(boolean startSslWebsocket) {
    method getSslWebsocketPort (line 186) | public int getSslWebsocketPort() {
    method setSslWebsocketPort (line 190) | public void setSslWebsocketPort(int sslWebsocketPort) {
    method getUseClientCA (line 194) | public boolean getUseClientCA() {
    method setUseClientCA (line 198) | public void setUseClientCA(boolean useClientCA) {
    method getSslKeyStoreType (line 202) | public String getSslKeyStoreType() {
    method setSslKeyStoreType (line 206) | public void setSslKeyStoreType(String sslKeyStoreType) {
    method getSslKeyFilePath (line 210) | public String getSslKeyFilePath() {
    method setSslKeyFilePath (line 214) | public void setSslKeyFilePath(String sslKeyFilePath) {
    method getSslManagerPwd (line 218) | public String getSslManagerPwd() {
    method setSslManagerPwd (line 222) | public void setSslManagerPwd(String sslManagerPwd) {
    method getSslStorePwd (line 226) | public String getSslStorePwd() {
    method setSslStorePwd (line 230) | public void setSslStorePwd(String sslStorePwd) {
    method getStartHttp (line 234) | public boolean getStartHttp() {
    method setStartHttp (line 238) | public void setStartHttp(boolean startHttp) {
    method getHttpPort (line 242) | public int getHttpPort() {
    method setHttpPort (line 246) | public void setHttpPort(int httpPort) {

FILE: jmqtt-support/src/main/java/org/jmqtt/support/exception/RemotingConnectException.java
  class RemotingConnectException (line 5) | public class RemotingConnectException extends RemotingException implemen...
    method RemotingConnectException (line 9) | public RemotingConnectException(String addr) {
    method RemotingConnectException (line 13) | public RemotingConnectException(String addr, Throwable throwable) {

FILE: jmqtt-support/src/main/java/org/jmqtt/support/exception/RemotingException.java
  class RemotingException (line 5) | public class RemotingException extends Exception implements Serializable {
    method RemotingException (line 8) | public RemotingException(String message){
    method RemotingException (line 12) | public RemotingException(String message,Throwable throwable){

FILE: jmqtt-support/src/main/java/org/jmqtt/support/exception/RemotingSendRequestException.java
  class RemotingSendRequestException (line 5) | public class RemotingSendRequestException extends RemotingException impl...
    method RemotingSendRequestException (line 7) | public RemotingSendRequestException(String message) {
    method RemotingSendRequestException (line 11) | public RemotingSendRequestException(String message, Throwable throwabl...

FILE: jmqtt-support/src/main/java/org/jmqtt/support/exception/RemotingTimeoutException.java
  class RemotingTimeoutException (line 5) | public class RemotingTimeoutException extends RemotingException implemen...
    method RemotingTimeoutException (line 9) | public RemotingTimeoutException(long timeoutMillis) {
    method RemotingTimeoutException (line 13) | public RemotingTimeoutException(long timeoutMillis, Throwable throwabl...

FILE: jmqtt-support/src/main/java/org/jmqtt/support/exception/RemotingTooMuchRequestException.java
  class RemotingTooMuchRequestException (line 5) | public class RemotingTooMuchRequestException  extends RemotingException ...
    method RemotingTooMuchRequestException (line 9) | public RemotingTooMuchRequestException(String message) {
    method RemotingTooMuchRequestException (line 13) | public RemotingTooMuchRequestException(String message, Throwable throw...

FILE: jmqtt-support/src/main/java/org/jmqtt/support/helper/MixAll.java
  class MixAll (line 24) | public class MixAll {
    method createIfNotExistsDir (line 28) | public static boolean createIfNotExistsDir(File file) {
    method dateFormater (line 32) | public static String dateFormater(long time) {
    method pluginInit (line 38) | public static <T> T pluginInit(String classFullName){
    method propToMap (line 49) | public static Map<String,Object> propToMap(String propStr) {
    method propToStr (line 56) | public static String propToStr(Map<String,Object> properties) {
    method isEmpty (line 63) | public static boolean isEmpty(Collection collection) {
    method getLocalIp (line 70) | public static String getLocalIp(){
    method uncompress (line 79) | public static byte[] uncompress(final byte[] src) throws IOException {
    method compress (line 118) | public static byte[] compress(final byte[] src, final int level) throw...
    method printProperties (line 141) | public static void printProperties(Logger log, Object obj) {
    method properties2POJO (line 161) | public static void properties2POJO(Properties properties, Object obj) {

FILE: jmqtt-support/src/main/java/org/jmqtt/support/helper/Pair.java
  class Pair (line 3) | public class Pair<T,K> {
    method Pair (line 7) | public Pair(T object1, K object2) {
    method getObject1 (line 12) | public T getObject1() {
    method setObject1 (line 16) | public void setObject1(T object1) {
    method getObject2 (line 20) | public K getObject2() {
    method setObject2 (line 24) | public void setObject2(K object2) {

FILE: jmqtt-support/src/main/java/org/jmqtt/support/helper/RejectHandler.java
  class RejectHandler (line 7) | public class RejectHandler implements RejectedExecutionHandler {
    method RejectHandler (line 11) | public RejectHandler(String task,int maxBlockQueueSize){
    method rejectedExecution (line 16) | @Override

FILE: jmqtt-support/src/main/java/org/jmqtt/support/helper/ThreadFactoryImpl.java
  class ThreadFactoryImpl (line 6) | public class ThreadFactoryImpl implements ThreadFactory {
    method ThreadFactoryImpl (line 11) | public ThreadFactoryImpl(String threadName){
    method newThread (line 15) | @Override

FILE: jmqtt-support/src/main/java/org/jmqtt/support/log/JmqttLogger.java
  type JmqttLogger (line 6) | public interface JmqttLogger {

FILE: jmqtt-support/src/main/java/org/jmqtt/support/log/LogUtil.java
  class LogUtil (line 5) | public class LogUtil {
    method debug (line 7) | public static void debug(Logger log,String desc,Object... param){
    method info (line 15) | public static void info(Logger log,String desc,Object... param){
    method warn (line 23) | public static void warn(Logger log,String desc,Object... param){
    method error (line 31) | public static void error(Logger log,String desc,Object... param){

FILE: jmqtt-support/src/main/java/org/jmqtt/support/remoting/RemotingHelper.java
  class RemotingHelper (line 15) | public class RemotingHelper {
    method closeChannel (line 19) | public static void closeChannel(Channel channel){
    method string2SocketAddress (line 29) | public static SocketAddress string2SocketAddress(final String addr){
    method getLocalAddr (line 35) | public static String getLocalAddr(){
    method normalizeHostAddress (line 78) | private static String normalizeHostAddress(final InetAddress localHost) {
    method getRemoteAddr (line 86) | public static String getRemoteAddr(Channel channel){

FILE: jmqtt-support/src/main/java/org/jmqtt/support/remoting/RemotingService.java
  type RemotingService (line 3) | public interface RemotingService {
    method start (line 8) | void start();
    method shutdown (line 13) | void shutdown();

FILE: jmqtt-tcp/src/main/java/org/jmqtt/App.java
  class App (line 7) | public class App
    method main (line 9) | public static void main( String[] args )
Condensed preview — 139 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (463K chars).
[
  {
    "path": ".dockerignore",
    "chars": 47,
    "preview": "rocksdb.db\n.git\n.env\n*.jpg\n*.png\n*.md\n**/target"
  },
  {
    "path": ".gitignore",
    "chars": 712,
    "preview": "storage/\nrun/\ntest_out/\ntmp/\ndoc/\n.directory\n.loadpath\n.metadata\n*.pydevproject\n*.bak\n*.swp\n*.tmp\n*~.nib\nlocal.propertie"
  },
  {
    "path": "Dockerfile",
    "chars": 511,
    "preview": "# Builder\nFROM maven:3-jdk-8-slim AS builder\n\nRUN mkdir /opt/jmqtt\nWORKDIR /opt/jmqtt\n\nADD . ./\n\nRUN mvn -Ppackage-all -"
  },
  {
    "path": "LICENSE",
    "chars": 11357,
    "preview": "                                 Apache License\n                           Version 2.0, January 2004\n                   "
  },
  {
    "path": "README.md",
    "chars": 1129,
    "preview": "**English** | [中文](README_CN.md)\n#  Jmqtt\n\n![Jmqtt logo](jmqtt.png)\n\n## Features\n* Full support of mqtt3.1.1 protocol\n* "
  },
  {
    "path": "README_CN.md",
    "chars": 667,
    "preview": "[English](README.md) | **中文**\n##  Jmqtt\n\n![Jmqtt logo](jmqtt.png)\n\n## 功能特性\n* 完整支持mqtt3.1.1协议\n* 支持基于mysql的数据持久化和集群\n* 支持友好"
  },
  {
    "path": "jmqtt-acceptance/README.md",
    "chars": 846,
    "preview": "这里是存放iot mq所有的功能测试,性能测试脚本及测试结果\n## 功能测试计划(基于集群)\n### 1.连接功能项\n1. 连接\n2. 连接 with cleasession = false(mqtt)\n3. 连接 with will 消息"
  },
  {
    "path": "jmqtt-acceptance/pom.xml",
    "chars": 1928,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project xmlns=\"http://maven.apache.org/POM/4.0.0\"\n         xmlns:xsi=\"http://www"
  },
  {
    "path": "jmqtt-admin/pom.xml",
    "chars": 822,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project xmlns=\"http://maven.apache.org/POM/4.0.0\"\n         xmlns:xsi=\"http://www"
  },
  {
    "path": "jmqtt-broker/pom.xml",
    "chars": 2199,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n\n<project xmlns=\"http://maven.apache.org/POM/4.0.0\" xmlns:xsi=\"http://www.w3.org/"
  },
  {
    "path": "jmqtt-broker/src/main/java/org/jmqtt/broker/BrokerController.java",
    "chars": 1257,
    "preview": "package org.jmqtt.broker;\n\nimport org.jmqtt.bus.BusController;\nimport org.jmqtt.mqtt.MQTTServer;\nimport org.jmqtt.suppor"
  },
  {
    "path": "jmqtt-broker/src/main/java/org/jmqtt/broker/BrokerStartup.java",
    "chars": 4805,
    "preview": "package org.jmqtt.broker;\n\nimport org.apache.commons.cli.*;\nimport org.apache.commons.lang3.StringUtils;\nimport org.apac"
  },
  {
    "path": "jmqtt-broker/src/main/resources/bin/jmqttshutdown",
    "chars": 310,
    "preview": "#!/bin/sh\n\npid=`ps ax | grep -i 'org.jmqtt.broker.BrokerStartup' |grep java | grep -v grep | awk '{print $1}'`\nif [ -z \""
  },
  {
    "path": "jmqtt-broker/src/main/resources/bin/runbroker.sh",
    "chars": 1091,
    "preview": "#!/bin/sh\n\n\nJAVA_OPT=\"${JAVA_OPT} -server -Xms1280m -Xmx1280m -Xmn640m\"\nJAVA_OPT=\"${JAVA_OPT} -XX:+UseG1GC -XX:G1HeapReg"
  },
  {
    "path": "jmqtt-broker/src/main/resources/conf/client.cer",
    "chars": 875,
    "preview": "-----BEGIN CERTIFICATE-----\nMIICWDCCAcGgAwIBAgIJANmxJ6Gxp3rbMA0GCSqGSIb3DQEBCwUAMEUxCzAJBgNV\nBAYTAkFVMRMwEQYDVQQIDApTb21"
  },
  {
    "path": "jmqtt-broker/src/main/resources/conf/jmqtt.properties",
    "chars": 674,
    "preview": "# 是否开启匿名访问\nanonymousEnable=true\n\n# max mqtt message size\nmaxMsgSize=524288\n\n\n# 是否开启tcp服务\nstartTcp=true\ntcpPort=1883\n# 是否"
  },
  {
    "path": "jmqtt-broker/src/main/resources/conf/jmqtt.sql",
    "chars": 3179,
    "preview": "DROP INDEX `uk_client_id` ON `jmqtt_session`;\nDROP INDEX `uk_client_id_topic` ON `jmqtt_subscription`;\nDROP INDEX `idx_c"
  },
  {
    "path": "jmqtt-broker/src/main/resources/conf/log4j2.xml",
    "chars": 9825,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n\n<configuration>\n\n    <properties>\n        <!-- log file path -->\n        <proper"
  },
  {
    "path": "jmqtt-broker/src/test/java/org/jmqtt/broker/test/Runtime.java",
    "chars": 2300,
    "preview": "package org.jmqtt.broker.test;\n\nimport com.sun.management.OperatingSystemMXBean;\nimport org.junit.jupiter.api.Test;\n\nimp"
  },
  {
    "path": "jmqtt-bus/README.md",
    "chars": 10,
    "preview": "iot 消息bus\n"
  },
  {
    "path": "jmqtt-bus/pom.xml",
    "chars": 1829,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n\n<project xmlns=\"http://maven.apache.org/POM/4.0.0\" xmlns:xsi=\"http://www.w3.org/"
  },
  {
    "path": "jmqtt-bus/src/main/java/org/jmqtt/bus/Authenticator.java",
    "chars": 302,
    "preview": "package org.jmqtt.bus;\n\n\npublic interface Authenticator {\n\n\n    boolean login(String clientId,String userName,byte[] pas"
  },
  {
    "path": "jmqtt-bus/src/main/java/org/jmqtt/bus/BusController.java",
    "chars": 2123,
    "preview": "package org.jmqtt.bus;\n\nimport org.jmqtt.bus.impl.*;\nimport org.jmqtt.bus.store.DBUtils;\nimport org.jmqtt.bus.subscripti"
  },
  {
    "path": "jmqtt-bus/src/main/java/org/jmqtt/bus/ClusterEventManager.java",
    "chars": 340,
    "preview": "package org.jmqtt.bus;\n\nimport org.jmqtt.bus.event.GatewayListener;\nimport org.jmqtt.bus.model.ClusterEvent;\n\n/**\n * clu"
  },
  {
    "path": "jmqtt-bus/src/main/java/org/jmqtt/bus/DeviceMessageManager.java",
    "chars": 665,
    "preview": "\npackage org.jmqtt.bus;\n\nimport org.jmqtt.bus.enums.MessageAckEnum;\nimport org.jmqtt.bus.model.DeviceMessage;\n\nimport ja"
  },
  {
    "path": "jmqtt-bus/src/main/java/org/jmqtt/bus/DeviceSessionManager.java",
    "chars": 250,
    "preview": "\npackage org.jmqtt.bus;\n\nimport org.jmqtt.bus.model.DeviceSession;\n\npublic interface DeviceSessionManager {\n\n\n\n    Devic"
  },
  {
    "path": "jmqtt-bus/src/main/java/org/jmqtt/bus/DeviceSubscriptionManager.java",
    "chars": 852,
    "preview": "package org.jmqtt.bus;\n\nimport org.jmqtt.bus.model.DeviceSubscription;\n\nimport java.util.Set;\n\npublic interface DeviceSu"
  },
  {
    "path": "jmqtt-bus/src/main/java/org/jmqtt/bus/enums/ClusterEventCodeEnum.java",
    "chars": 371,
    "preview": "package org.jmqtt.bus.enums;\n\n/**\n * cluster event code\n */\npublic enum ClusterEventCodeEnum {\n\n    MQTT_CLEAR_SESSION(\""
  },
  {
    "path": "jmqtt-bus/src/main/java/org/jmqtt/bus/enums/DeviceOnlineStateEnum.java",
    "chars": 282,
    "preview": "\npackage org.jmqtt.bus.enums;\n\npublic enum DeviceOnlineStateEnum {\n\n    ONLINE(\"ONLINE\"),\n\n    OFFLINE(\"OFFLINE\"),\n\n    "
  },
  {
    "path": "jmqtt-bus/src/main/java/org/jmqtt/bus/enums/MessageAckEnum.java",
    "chars": 478,
    "preview": "\npackage org.jmqtt.bus.enums;\n\npublic enum MessageAckEnum {\n\n    UN_ACK(0),\n\n    ACK(1),\n\n    ;\n\n    private int code;\n\n"
  },
  {
    "path": "jmqtt-bus/src/main/java/org/jmqtt/bus/enums/MessageFlowEnum.java",
    "chars": 282,
    "preview": "package org.jmqtt.bus.enums;\n\n\npublic enum MessageFlowEnum {\n\n    INBOUND(\"INBOUND\"),\n\n    OUTBOUND(\"OUTBOUND\"),\n\n      "
  },
  {
    "path": "jmqtt-bus/src/main/java/org/jmqtt/bus/enums/MessageSourceEnum.java",
    "chars": 284,
    "preview": "package org.jmqtt.bus.enums;\n\n\npublic enum MessageSourceEnum {\n\n    DEVICE(\"DEVICE\"),\n\n    PLATFORM(\"PLATFORM\"),\n\n      "
  },
  {
    "path": "jmqtt-bus/src/main/java/org/jmqtt/bus/enums/TransportProtocolEnum.java",
    "chars": 290,
    "preview": "\npackage org.jmqtt.bus.enums;\n\n\npublic enum TransportProtocolEnum {\n\n    MQTT(\"MQTT\"),\n\n    TCP(\"TCP\"),\n\n    COAP(\"COAP\""
  },
  {
    "path": "jmqtt-bus/src/main/java/org/jmqtt/bus/event/EventCenter.java",
    "chars": 636,
    "preview": "package org.jmqtt.bus.event;\n\nimport com.google.common.eventbus.EventBus;\nimport org.jmqtt.bus.model.ClusterEvent;\n\npubl"
  },
  {
    "path": "jmqtt-bus/src/main/java/org/jmqtt/bus/event/GatewayListener.java",
    "chars": 224,
    "preview": "\npackage org.jmqtt.bus.event;\n\nimport org.jmqtt.bus.model.ClusterEvent;\n\n/**\n * gateway listener\n * iot gateway 实现该接口,接收"
  },
  {
    "path": "jmqtt-bus/src/main/java/org/jmqtt/bus/impl/ClusterEventManagerImpl.java",
    "chars": 6519,
    "preview": "package org.jmqtt.bus.impl;\n\nimport com.alibaba.fastjson.JSONObject;\nimport org.apache.ibatis.session.SqlSession;\nimport"
  },
  {
    "path": "jmqtt-bus/src/main/java/org/jmqtt/bus/impl/DefaultAuthenticator.java",
    "chars": 572,
    "preview": "package org.jmqtt.bus.impl;\n\nimport org.jmqtt.bus.Authenticator;\n\n\npublic class DefaultAuthenticator implements Authenti"
  },
  {
    "path": "jmqtt-bus/src/main/java/org/jmqtt/bus/impl/DeviceMessageManagerImpl.java",
    "chars": 7902,
    "preview": "package org.jmqtt.bus.impl;\n\nimport com.alibaba.fastjson.JSONObject;\nimport org.apache.ibatis.session.SqlSession;\nimport"
  },
  {
    "path": "jmqtt-bus/src/main/java/org/jmqtt/bus/impl/DeviceSessionManagerImpl.java",
    "chars": 3560,
    "preview": "package org.jmqtt.bus.impl;\n\nimport com.alibaba.fastjson.JSONObject;\nimport org.apache.ibatis.session.SqlSession;\nimport"
  },
  {
    "path": "jmqtt-bus/src/main/java/org/jmqtt/bus/impl/DeviceSubscriptionManagerImpl.java",
    "chars": 6440,
    "preview": "package org.jmqtt.bus.impl;\n\nimport com.alibaba.fastjson.JSONObject;\nimport org.apache.ibatis.session.SqlSession;\nimport"
  },
  {
    "path": "jmqtt-bus/src/main/java/org/jmqtt/bus/model/ClusterEvent.java",
    "chars": 1805,
    "preview": "package org.jmqtt.bus.model;\n\nimport org.jmqtt.bus.enums.ClusterEventCodeEnum;\nimport org.jmqtt.bus.subscription.model.S"
  },
  {
    "path": "jmqtt-bus/src/main/java/org/jmqtt/bus/model/DeviceInboxMessage.java",
    "chars": 1110,
    "preview": "package org.jmqtt.bus.model;\n\nimport java.util.Date;\n\npublic class DeviceInboxMessage {\n\n    private Long id;\n\n    priva"
  },
  {
    "path": "jmqtt-bus/src/main/java/org/jmqtt/bus/model/DeviceMessage.java",
    "chars": 1664,
    "preview": "\npackage org.jmqtt.bus.model;\n\nimport org.jmqtt.bus.enums.MessageSourceEnum;\n\nimport java.util.Date;\nimport java.util.Ma"
  },
  {
    "path": "jmqtt-bus/src/main/java/org/jmqtt/bus/model/DeviceSession.java",
    "chars": 1942,
    "preview": "\npackage org.jmqtt.bus.model;\n\nimport org.jmqtt.bus.enums.DeviceOnlineStateEnum;\nimport org.jmqtt.bus.enums.TransportPro"
  },
  {
    "path": "jmqtt-bus/src/main/java/org/jmqtt/bus/model/DeviceSubscription.java",
    "chars": 1067,
    "preview": "package org.jmqtt.bus.model;\n\nimport java.util.Date;\nimport java.util.Map;\n\npublic class DeviceSubscription {\n\n    priva"
  },
  {
    "path": "jmqtt-bus/src/main/java/org/jmqtt/bus/store/AbstractDBStore.java",
    "chars": 883,
    "preview": "package org.jmqtt.bus.store;\n\nimport org.apache.ibatis.session.SqlSession;\nimport org.jmqtt.bus.store.mapper.*;\nimport o"
  },
  {
    "path": "jmqtt-bus/src/main/java/org/jmqtt/bus/store/DBCallback.java",
    "chars": 150,
    "preview": "package org.jmqtt.bus.store;\n\nimport org.apache.ibatis.session.SqlSession;\n\npublic interface DBCallback {\n    <T> T oper"
  },
  {
    "path": "jmqtt-bus/src/main/java/org/jmqtt/bus/store/DBUtils.java",
    "chars": 4672,
    "preview": "package org.jmqtt.bus.store;\n\nimport com.alibaba.druid.pool.DruidDataSource;\nimport org.apache.ibatis.datasource.DataSou"
  },
  {
    "path": "jmqtt-bus/src/main/java/org/jmqtt/bus/store/daoobject/DeviceInboxMessageDO.java",
    "chars": 1243,
    "preview": "package org.jmqtt.bus.store.daoobject;\n\nimport java.io.Serializable;\nimport java.util.Date;\n\npublic class DeviceInboxMes"
  },
  {
    "path": "jmqtt-bus/src/main/java/org/jmqtt/bus/store/daoobject/EventDO.java",
    "chars": 1071,
    "preview": "package org.jmqtt.bus.store.daoobject;\n\nimport java.io.Serializable;\nimport java.util.Date;\n\npublic class EventDO implem"
  },
  {
    "path": "jmqtt-bus/src/main/java/org/jmqtt/bus/store/daoobject/MessageDO.java",
    "chars": 1459,
    "preview": "package org.jmqtt.bus.store.daoobject;\n\nimport java.io.Serializable;\nimport java.util.Date;\n\npublic class MessageDO impl"
  },
  {
    "path": "jmqtt-bus/src/main/java/org/jmqtt/bus/store/daoobject/RetainMessageDO.java",
    "chars": 1295,
    "preview": "package org.jmqtt.bus.store.daoobject;\n\nimport java.io.Serializable;\nimport java.util.Date;\n\npublic class RetainMessageD"
  },
  {
    "path": "jmqtt-bus/src/main/java/org/jmqtt/bus/store/daoobject/SessionDO.java",
    "chars": 1934,
    "preview": "\npackage org.jmqtt.bus.store.daoobject;\n\nimport java.io.Serializable;\nimport java.util.Date;\n\npublic class SessionDO imp"
  },
  {
    "path": "jmqtt-bus/src/main/java/org/jmqtt/bus/store/daoobject/SubscriptionDO.java",
    "chars": 1110,
    "preview": "package org.jmqtt.bus.store.daoobject;\n\nimport java.io.Serializable;\nimport java.util.Date;\n\npublic class SubscriptionDO"
  },
  {
    "path": "jmqtt-bus/src/main/java/org/jmqtt/bus/store/mapper/ClientInboxMessageMapper.java",
    "chars": 1101,
    "preview": "package org.jmqtt.bus.store.mapper;\n\nimport org.apache.ibatis.annotations.*;\nimport org.jmqtt.bus.store.daoobject.Device"
  },
  {
    "path": "jmqtt-bus/src/main/java/org/jmqtt/bus/store/mapper/EventMapper.java",
    "chars": 904,
    "preview": "package org.jmqtt.bus.store.mapper;\n\nimport org.apache.ibatis.annotations.Insert;\nimport org.apache.ibatis.annotations.O"
  },
  {
    "path": "jmqtt-bus/src/main/java/org/jmqtt/bus/store/mapper/MessageMapper.java",
    "chars": 1174,
    "preview": "package org.jmqtt.bus.store.mapper;\n\nimport org.apache.ibatis.annotations.Insert;\nimport org.apache.ibatis.annotations.O"
  },
  {
    "path": "jmqtt-bus/src/main/java/org/jmqtt/bus/store/mapper/RetainMessageMapper.java",
    "chars": 1058,
    "preview": "package org.jmqtt.bus.store.mapper;\n\nimport org.apache.ibatis.annotations.Delete;\nimport org.apache.ibatis.annotations.I"
  },
  {
    "path": "jmqtt-bus/src/main/java/org/jmqtt/bus/store/mapper/SessionMapper.java",
    "chars": 1306,
    "preview": "\npackage org.jmqtt.bus.store.mapper;\n\nimport org.apache.ibatis.annotations.Insert;\nimport org.apache.ibatis.annotations."
  },
  {
    "path": "jmqtt-bus/src/main/java/org/jmqtt/bus/store/mapper/SubscriptionMapper.java",
    "chars": 1061,
    "preview": "package org.jmqtt.bus.store.mapper;\n\nimport org.apache.ibatis.annotations.*;\nimport org.jmqtt.bus.store.daoobject.Subscr"
  },
  {
    "path": "jmqtt-bus/src/main/java/org/jmqtt/bus/subscription/CNode.java",
    "chars": 3396,
    "preview": "package org.jmqtt.bus.subscription;\n\nimport org.jmqtt.bus.subscription.model.Subscription;\n\nimport java.util.*;\n\npublic "
  },
  {
    "path": "jmqtt-bus/src/main/java/org/jmqtt/bus/subscription/CTrie.java",
    "chars": 8472,
    "preview": "package org.jmqtt.bus.subscription;\n\nimport org.jmqtt.bus.subscription.model.Subscription;\n\nimport java.util.Collections"
  },
  {
    "path": "jmqtt-bus/src/main/java/org/jmqtt/bus/subscription/CTrieSubscriptionMatcher.java",
    "chars": 1070,
    "preview": "package org.jmqtt.bus.subscription;\n\nimport org.jmqtt.bus.subscription.model.Subscription;\nimport org.jmqtt.support.log."
  },
  {
    "path": "jmqtt-bus/src/main/java/org/jmqtt/bus/subscription/DefaultSubscriptionTreeMatcher.java",
    "chars": 7000,
    "preview": "package org.jmqtt.bus.subscription;\n\nimport org.jmqtt.bus.subscription.model.Subscription;\nimport org.jmqtt.support.log."
  },
  {
    "path": "jmqtt-bus/src/main/java/org/jmqtt/bus/subscription/DumpTreeVisitor.java",
    "chars": 1687,
    "preview": "package org.jmqtt.bus.subscription;\n\nimport io.netty.util.internal.StringUtil;\nimport org.jmqtt.bus.subscription.model.S"
  },
  {
    "path": "jmqtt-bus/src/main/java/org/jmqtt/bus/subscription/INode.java",
    "chars": 795,
    "preview": "package org.jmqtt.bus.subscription;\n\nimport java.util.concurrent.atomic.AtomicReference;\n\npublic class INode {\n    priva"
  },
  {
    "path": "jmqtt-bus/src/main/java/org/jmqtt/bus/subscription/SubscriptionCounterVisitor.java",
    "chars": 436,
    "preview": "package org.jmqtt.bus.subscription;\n\nimport java.util.concurrent.atomic.AtomicInteger;\n\nclass SubscriptionCounterVisitor"
  },
  {
    "path": "jmqtt-bus/src/main/java/org/jmqtt/bus/subscription/SubscriptionMatcher.java",
    "chars": 681,
    "preview": "package org.jmqtt.bus.subscription;\n\nimport org.jmqtt.bus.subscription.model.Subscription;\n\nimport java.util.Set;\n\n/**\n "
  },
  {
    "path": "jmqtt-bus/src/main/java/org/jmqtt/bus/subscription/TNode.java",
    "chars": 1986,
    "preview": "/*\n * Copyright (c) 2012-2018 The original author or authors\n * ------------------------------------------------------\n "
  },
  {
    "path": "jmqtt-bus/src/main/java/org/jmqtt/bus/subscription/Token.java",
    "chars": 1244,
    "preview": "package org.jmqtt.bus.subscription;\n\n\npublic class Token {\n\n    public static final Token EMPTY = new Token(\"\");\n    pub"
  },
  {
    "path": "jmqtt-bus/src/main/java/org/jmqtt/bus/subscription/Topic.java",
    "chars": 5651,
    "preview": "package org.jmqtt.bus.subscription;\n\nimport org.jmqtt.support.log.JmqttLogger;\nimport org.jmqtt.support.log.LogUtil;\nimp"
  },
  {
    "path": "jmqtt-bus/src/main/java/org/jmqtt/bus/subscription/model/Subscription.java",
    "chars": 2065,
    "preview": "package org.jmqtt.bus.subscription.model;\n\nimport java.util.Map;\nimport java.util.Objects;\n\n/**\n * 订阅关系\n */\npublic class"
  },
  {
    "path": "jmqtt-bus/src/test/java/org/jmqtt/AppTest.java",
    "chars": 308,
    "preview": "package org.jmqtt;\n\nimport org.junit.jupiter.api.Test;\n\nimport static org.junit.jupiter.api.Assertions.assertTrue;\n\n/**\n"
  },
  {
    "path": "jmqtt-bus/src/test/java/org/jmqtt/bus/subscription/CTrieSubscriptionMatcherMatchingTest.java",
    "chars": 8764,
    "preview": "/*\n * Copyright (c) 2012-2018 The original author or authors\n * ------------------------------------------------------\n "
  },
  {
    "path": "jmqtt-bus/src/test/java/org/jmqtt/bus/subscription/CTrieTest.java",
    "chars": 12403,
    "preview": "/*\n * Copyright (c) 2012-2018 The original author or authors\n * ------------------------------------------------------\n "
  },
  {
    "path": "jmqtt-bus/src/test/java/org/jmqtt/bus/subscription/TopicTest.java",
    "chars": 6193,
    "preview": "/*\n * Copyright (c) 2012-2018 The original author or authors\n * ------------------------------------------------------\n "
  },
  {
    "path": "jmqtt-doc/README.md",
    "chars": 155,
    "preview": "官网搭建指导文件\n\n1. clone本项目\n2. 安装npm或yarn,具体参考:https://www.vuepress.cn/guide/getting-started.html\n3. 执行:yarn add -D vuepress\n4"
  },
  {
    "path": "jmqtt-doc/docs/README.md",
    "chars": 534,
    "preview": "## 快速上手\n\n### 安装主题\n\n1. 下载 [release](https://github.com/Cicizz/jmqtt/releases)(3.x以上版本) 或`clone`本项目:\n2. 在jmqtt根目录执行::\n```b"
  },
  {
    "path": "jmqtt-doc/docs/TEST_REPORT.md",
    "chars": 16577,
    "preview": "# Jmqtt 最新版功能及性能测试报告\n线上版连接:https://www.yuque.com/tristan-ku8np/zze/xghq80\n最新版链接:[https://github.com/Cicizz/jmqtt](https:"
  },
  {
    "path": "jmqtt-doc/pom.xml",
    "chars": 587,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n\n<project xmlns=\"http://maven.apache.org/POM/4.0.0\" xmlns:xsi=\"http://www.w3.org/"
  },
  {
    "path": "jmqtt-example/pom.xml",
    "chars": 980,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n\n<project xmlns=\"http://maven.apache.org/POM/4.0.0\" xmlns:xsi=\"http://www.w3.org/"
  },
  {
    "path": "jmqtt-example/src/main/java/org/jmqtt/java/Consumer.java",
    "chars": 1709,
    "preview": "package org.jmqtt.java;\n\nimport org.eclipse.paho.client.mqttv3.*;\nimport org.eclipse.paho.client.mqttv3.persist.MemoryPe"
  },
  {
    "path": "jmqtt-example/src/main/java/org/jmqtt/java/Producer.java",
    "chars": 1789,
    "preview": "package org.jmqtt.java;\n\nimport org.eclipse.paho.client.mqttv3.MqttClient;\nimport org.eclipse.paho.client.mqttv3.MqttCon"
  },
  {
    "path": "jmqtt-example/src/main/java/org/jmqtt/websocket/paho-mqtt-min.js",
    "chars": 31310,
    "preview": "/*******************************************************************************\n * Copyright (c) 2013, 2014 IBM Corp.\n "
  },
  {
    "path": "jmqtt-example/src/main/java/org/jmqtt/websocket/paho-mqtt.js",
    "chars": 79309,
    "preview": "/*******************************************************************************\n * Copyright (c) 2013 IBM Corp.\n *\n * A"
  },
  {
    "path": "jmqtt-example/src/main/java/org/jmqtt/websocket/webSocket.html",
    "chars": 3680,
    "preview": "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n    <meta charset=\"UTF-8\">\n    <title>webSocket test</title>\n</head>\n<body>\n<h1>"
  },
  {
    "path": "jmqtt-manager",
    "chars": 1,
    "preview": "\n"
  },
  {
    "path": "jmqtt-mqtt/README.md",
    "chars": 21,
    "preview": "Support mqtt gateway\n"
  },
  {
    "path": "jmqtt-mqtt/pom.xml",
    "chars": 1138,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project xmlns=\"http://maven.apache.org/POM/4.0.0\" xmlns:xsi=\"http://www.w3.org/2"
  },
  {
    "path": "jmqtt-mqtt/src/main/java/org/jmqtt/mqtt/ConnectManager.java",
    "chars": 1146,
    "preview": "package org.jmqtt.mqtt;\n\nimport java.util.Collection;\nimport java.util.Map;\nimport java.util.concurrent.ConcurrentHashMa"
  },
  {
    "path": "jmqtt-mqtt/src/main/java/org/jmqtt/mqtt/MQTTConnection.java",
    "chars": 21615,
    "preview": "\npackage org.jmqtt.mqtt;\n\nimport io.netty.channel.Channel;\nimport io.netty.channel.ChannelFuture;\nimport io.netty.channe"
  },
  {
    "path": "jmqtt-mqtt/src/main/java/org/jmqtt/mqtt/MQTTConnectionFactory.java",
    "chars": 5667,
    "preview": "\npackage org.jmqtt.mqtt;\n\nimport io.netty.channel.Channel;\nimport io.netty.handler.codec.mqtt.MqttMessageType;\nimport or"
  },
  {
    "path": "jmqtt-mqtt/src/main/java/org/jmqtt/mqtt/MQTTServer.java",
    "chars": 1893,
    "preview": "\npackage org.jmqtt.mqtt;\n\nimport io.netty.handler.codec.mqtt.MqttConnectReturnCode;\nimport org.jmqtt.bus.BusController;\n"
  },
  {
    "path": "jmqtt-mqtt/src/main/java/org/jmqtt/mqtt/codec/ByteBuf2WebSocketEncoder.java",
    "chars": 619,
    "preview": "package org.jmqtt.mqtt.codec;\n\nimport io.netty.buffer.ByteBuf;\nimport io.netty.channel.ChannelHandlerContext;\nimport io."
  },
  {
    "path": "jmqtt-mqtt/src/main/java/org/jmqtt/mqtt/codec/WebSocket2ByteBufDecoder.java",
    "chars": 638,
    "preview": "package org.jmqtt.mqtt.codec;\n\nimport io.netty.buffer.ByteBuf;\nimport io.netty.channel.ChannelHandlerContext;\nimport io."
  },
  {
    "path": "jmqtt-mqtt/src/main/java/org/jmqtt/mqtt/event/MqttEventListener.java",
    "chars": 4720,
    "preview": "package org.jmqtt.mqtt.event;\n\nimport com.alibaba.fastjson.JSONObject;\nimport com.google.common.eventbus.Subscribe;\nimpo"
  },
  {
    "path": "jmqtt-mqtt/src/main/java/org/jmqtt/mqtt/model/MqttTopic.java",
    "chars": 508,
    "preview": "package org.jmqtt.mqtt.model;\n\npublic class MqttTopic {\n\n    private String topicName;\n    private int    qos;\n\n    publ"
  },
  {
    "path": "jmqtt-mqtt/src/main/java/org/jmqtt/mqtt/netty/MqttNettyUtils.java",
    "chars": 3111,
    "preview": "package org.jmqtt.mqtt.netty;\n\nimport io.netty.channel.Channel;\nimport io.netty.channel.ChannelHandlerContext;\nimport io"
  },
  {
    "path": "jmqtt-mqtt/src/main/java/org/jmqtt/mqtt/netty/MqttRemotingServer.java",
    "chars": 9441,
    "preview": "package org.jmqtt.mqtt.netty;\n\nimport io.netty.bootstrap.ServerBootstrap;\nimport io.netty.buffer.PooledByteBufAllocator;"
  },
  {
    "path": "jmqtt-mqtt/src/main/java/org/jmqtt/mqtt/netty/NettyMqttHandler.java",
    "chars": 2883,
    "preview": "\npackage org.jmqtt.mqtt.netty;\n\nimport io.netty.channel.ChannelDuplexHandler;\nimport io.netty.channel.ChannelFuture;\nimp"
  },
  {
    "path": "jmqtt-mqtt/src/main/java/org/jmqtt/mqtt/netty/NettySslHandler.java",
    "chars": 2488,
    "preview": "package org.jmqtt.mqtt.netty;\n\nimport io.netty.channel.ChannelHandler;\nimport io.netty.channel.socket.SocketChannel;\nimp"
  },
  {
    "path": "jmqtt-mqtt/src/main/java/org/jmqtt/mqtt/protocol/RequestProcessor.java",
    "chars": 328,
    "preview": "package org.jmqtt.mqtt.protocol;\n\nimport io.netty.channel.ChannelHandlerContext;\nimport io.netty.handler.codec.mqtt.Mqtt"
  },
  {
    "path": "jmqtt-mqtt/src/main/java/org/jmqtt/mqtt/protocol/impl/ConnectProcessor.java",
    "chars": 4974,
    "preview": "package org.jmqtt.mqtt.protocol.impl;\n\nimport io.netty.channel.ChannelFuture;\nimport io.netty.channel.ChannelFutureListe"
  },
  {
    "path": "jmqtt-mqtt/src/main/java/org/jmqtt/mqtt/protocol/impl/DisconnectProcessor.java",
    "chars": 1015,
    "preview": "package org.jmqtt.mqtt.protocol.impl;\n\nimport io.netty.channel.ChannelHandlerContext;\nimport io.netty.handler.codec.mqtt"
  },
  {
    "path": "jmqtt-mqtt/src/main/java/org/jmqtt/mqtt/protocol/impl/PingProcessor.java",
    "chars": 528,
    "preview": "package org.jmqtt.mqtt.protocol.impl;\n\nimport io.netty.channel.ChannelHandlerContext;\nimport io.netty.handler.codec.mqtt"
  },
  {
    "path": "jmqtt-mqtt/src/main/java/org/jmqtt/mqtt/protocol/impl/PubAckProcessor.java",
    "chars": 747,
    "preview": "package org.jmqtt.mqtt.protocol.impl;\n\nimport io.netty.channel.ChannelHandlerContext;\nimport io.netty.handler.codec.mqtt"
  },
  {
    "path": "jmqtt-mqtt/src/main/java/org/jmqtt/mqtt/protocol/impl/PubCompProcessor.java",
    "chars": 619,
    "preview": "package org.jmqtt.mqtt.protocol.impl;\n\nimport io.netty.channel.ChannelHandlerContext;\nimport io.netty.handler.codec.mqtt"
  },
  {
    "path": "jmqtt-mqtt/src/main/java/org/jmqtt/mqtt/protocol/impl/PubRecProcessor.java",
    "chars": 636,
    "preview": "package org.jmqtt.mqtt.protocol.impl;\n\nimport io.netty.channel.ChannelHandlerContext;\nimport io.netty.handler.codec.mqtt"
  },
  {
    "path": "jmqtt-mqtt/src/main/java/org/jmqtt/mqtt/protocol/impl/PubRelProcessor.java",
    "chars": 1127,
    "preview": "package org.jmqtt.mqtt.protocol.impl;\n\nimport io.netty.channel.ChannelHandlerContext;\nimport io.netty.handler.codec.mqtt"
  },
  {
    "path": "jmqtt-mqtt/src/main/java/org/jmqtt/mqtt/protocol/impl/PublishProcessor.java",
    "chars": 1219,
    "preview": "package org.jmqtt.mqtt.protocol.impl;\n\nimport io.netty.channel.ChannelHandlerContext;\nimport io.netty.handler.codec.mqtt"
  },
  {
    "path": "jmqtt-mqtt/src/main/java/org/jmqtt/mqtt/protocol/impl/SubscribeProcessor.java",
    "chars": 761,
    "preview": "package org.jmqtt.mqtt.protocol.impl;\n\nimport io.netty.channel.ChannelHandlerContext;\nimport io.netty.handler.codec.mqtt"
  },
  {
    "path": "jmqtt-mqtt/src/main/java/org/jmqtt/mqtt/protocol/impl/UnSubscribeProcessor.java",
    "chars": 752,
    "preview": "package org.jmqtt.mqtt.protocol.impl;\n\nimport io.netty.channel.ChannelHandlerContext;\nimport io.netty.handler.codec.mqtt"
  },
  {
    "path": "jmqtt-mqtt/src/main/java/org/jmqtt/mqtt/retain/RetainMessageHandler.java",
    "chars": 293,
    "preview": "package org.jmqtt.mqtt.retain;\n\nimport org.jmqtt.bus.model.DeviceMessage;\n\nimport java.util.List;\n\npublic interface Reta"
  },
  {
    "path": "jmqtt-mqtt/src/main/java/org/jmqtt/mqtt/retain/impl/RetainMessageHandlerImpl.java",
    "chars": 3253,
    "preview": "package org.jmqtt.mqtt.retain.impl;\n\nimport org.apache.ibatis.session.SqlSession;\nimport org.jmqtt.bus.model.DeviceMessa"
  },
  {
    "path": "jmqtt-mqtt/src/main/java/org/jmqtt/mqtt/session/MqttSession.java",
    "chars": 5818,
    "preview": "\npackage org.jmqtt.mqtt.session;\n\nimport io.netty.handler.codec.mqtt.MqttPublishMessage;\nimport io.netty.handler.codec.m"
  },
  {
    "path": "jmqtt-mqtt/src/main/java/org/jmqtt/mqtt/utils/MqttMessageUtil.java",
    "chars": 4885,
    "preview": "package org.jmqtt.mqtt.utils;\n\nimport io.netty.buffer.ByteBuf;\nimport io.netty.buffer.Unpooled;\nimport io.netty.handler."
  },
  {
    "path": "jmqtt-mqtt/src/main/java/org/jmqtt/mqtt/utils/MqttMsgHeader.java",
    "chars": 1463,
    "preview": "package org.jmqtt.mqtt.utils;\n\nimport io.netty.handler.codec.mqtt.MqttPublishMessage;\nimport org.jmqtt.bus.enums.Message"
  },
  {
    "path": "jmqtt-support/README.md",
    "chars": 25,
    "preview": "通用支持,支持消息总线模块Bus及各协议网关处理\n"
  },
  {
    "path": "jmqtt-support/pom.xml",
    "chars": 688,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n\n<project xmlns=\"http://maven.apache.org/POM/4.0.0\" xmlns:xsi=\"http://www.w3.org/"
  },
  {
    "path": "jmqtt-support/src/main/java/org/jmqtt/support/config/BrokerConfig.java",
    "chars": 2978,
    "preview": "package org.jmqtt.support.config;\n\nimport java.io.File;\n\npublic class BrokerConfig {\n\n    // 配置conf文件的所在位置,logback,prope"
  },
  {
    "path": "jmqtt-support/src/main/java/org/jmqtt/support/config/NettyConfig.java",
    "chars": 5610,
    "preview": "package org.jmqtt.support.config;\n\npublic class NettyConfig {\n    private int tcpBackLog = 1024;\n    private boolean tcp"
  },
  {
    "path": "jmqtt-support/src/main/java/org/jmqtt/support/exception/RemotingConnectException.java",
    "chars": 456,
    "preview": "package org.jmqtt.support.exception;\n\nimport java.io.Serializable;\n\npublic class RemotingConnectException extends Remoti"
  },
  {
    "path": "jmqtt-support/src/main/java/org/jmqtt/support/exception/RemotingException.java",
    "chars": 400,
    "preview": "package org.jmqtt.support.exception;\n\nimport java.io.Serializable;\n\npublic class RemotingException extends Exception imp"
  },
  {
    "path": "jmqtt-support/src/main/java/org/jmqtt/support/exception/RemotingSendRequestException.java",
    "chars": 378,
    "preview": "package org.jmqtt.support.exception;\n\nimport java.io.Serializable;\n\npublic class RemotingSendRequestException extends Re"
  },
  {
    "path": "jmqtt-support/src/main/java/org/jmqtt/support/exception/RemotingTimeoutException.java",
    "chars": 488,
    "preview": "package org.jmqtt.support.exception;\n\nimport java.io.Serializable;\n\npublic class RemotingTimeoutException extends Remoti"
  },
  {
    "path": "jmqtt-support/src/main/java/org/jmqtt/support/exception/RemotingTooMuchRequestException.java",
    "chars": 456,
    "preview": "package org.jmqtt.support.exception;\n\nimport java.io.Serializable;\n\npublic class RemotingTooMuchRequestException  extend"
  },
  {
    "path": "jmqtt-support/src/main/java/org/jmqtt/support/helper/MixAll.java",
    "chars": 7170,
    "preview": "package org.jmqtt.support.helper;\n\nimport com.alibaba.fastjson.JSONObject;\nimport org.jmqtt.support.log.LogUtil;\nimport "
  },
  {
    "path": "jmqtt-support/src/main/java/org/jmqtt/support/helper/Pair.java",
    "chars": 495,
    "preview": "package org.jmqtt.support.helper;\n\npublic class Pair<T,K> {\n    private T object1;\n    private K object2;\n\n    public Pa"
  },
  {
    "path": "jmqtt-support/src/main/java/org/jmqtt/support/helper/RejectHandler.java",
    "chars": 716,
    "preview": "package org.jmqtt.support.helper;\n\nimport java.util.concurrent.RejectedExecutionException;\nimport java.util.concurrent.R"
  },
  {
    "path": "jmqtt-support/src/main/java/org/jmqtt/support/helper/ThreadFactoryImpl.java",
    "chars": 516,
    "preview": "package org.jmqtt.support.helper;\n\nimport java.util.concurrent.ThreadFactory;\nimport java.util.concurrent.atomic.AtomicI"
  },
  {
    "path": "jmqtt-support/src/main/java/org/jmqtt/support/log/JmqttLogger.java",
    "chars": 624,
    "preview": "package org.jmqtt.support.log;\n\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\npublic interface JmqttLogger {"
  },
  {
    "path": "jmqtt-support/src/main/java/org/jmqtt/support/log/LogUtil.java",
    "chars": 905,
    "preview": "package org.jmqtt.support.log;\n\nimport org.slf4j.Logger;\n\npublic class LogUtil {\n\n    public static void debug(Logger lo"
  },
  {
    "path": "jmqtt-support/src/main/java/org/jmqtt/support/remoting/RemotingHelper.java",
    "chars": 3642,
    "preview": "package org.jmqtt.support.remoting;\n\n\nimport io.netty.channel.Channel;\nimport io.netty.channel.ChannelFuture;\nimport io."
  },
  {
    "path": "jmqtt-support/src/main/java/org/jmqtt/support/remoting/RemotingService.java",
    "chars": 195,
    "preview": "package org.jmqtt.support.remoting;\n\npublic interface RemotingService {\n\n    /**\n     * remoting start\n     */\n    void "
  },
  {
    "path": "jmqtt-tcp/README.md",
    "chars": 15,
    "preview": "自定义tcp协议网关\n待实现\n"
  },
  {
    "path": "jmqtt-tcp/pom.xml",
    "chars": 507,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n\n<project xmlns=\"http://maven.apache.org/POM/4.0.0\" xmlns:xsi=\"http://www.w3.org/"
  },
  {
    "path": "jmqtt-tcp/src/main/java/org/jmqtt/App.java",
    "chars": 172,
    "preview": "package org.jmqtt;\n\n/**\n * Hello world!\n *\n */\npublic class App \n{\n    public static void main( String[] args )\n    {\n  "
  },
  {
    "path": "pom.xml",
    "chars": 8340,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n\n<project xmlns=\"http://maven.apache.org/POM/4.0.0\" xmlns:xsi=\"http://www.w3.org/"
  }
]

// ... and 1 more files (download for full content)

About this extraction

This page contains the full source code of the Cicizz/jmqtt GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 139 files (414.7 KB), approximately 106.7k tokens, and a symbol index with 896 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!