25 changed files with 2083 additions and 17 deletions
@ -0,0 +1,7 @@ |
|||||
|
.idea/ |
||||
|
*.ipr |
||||
|
*.iws |
||||
|
*.ids |
||||
|
*.iml |
||||
|
logs |
||||
|
target |
||||
@ -0,0 +1,99 @@ |
|||||
|
<!-- |
||||
|
|
||||
|
Copyright © 2016-2018 The Thingsboard Authors |
||||
|
|
||||
|
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. |
||||
|
|
||||
|
--> |
||||
|
<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"> |
||||
|
<modelVersion>4.0.0</modelVersion> |
||||
|
<parent> |
||||
|
<groupId>org.thingsboard</groupId> |
||||
|
<version>1.4.1-SNAPSHOT</version> |
||||
|
<artifactId>thingsboard</artifactId> |
||||
|
</parent> |
||||
|
<groupId>org.thingsboard</groupId> |
||||
|
<artifactId>netty-mqtt</artifactId> |
||||
|
<version>1.4.1-SNAPSHOT</version> |
||||
|
<packaging>jar</packaging> |
||||
|
|
||||
|
<name>Netty MQTT Client</name> |
||||
|
<url>https://thingsboard.io</url> |
||||
|
|
||||
|
<properties> |
||||
|
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> |
||||
|
<main.dir>${basedir}/..</main.dir> |
||||
|
</properties> |
||||
|
|
||||
|
<dependencies> |
||||
|
<dependency> |
||||
|
<groupId>io.netty</groupId> |
||||
|
<artifactId>netty-codec-mqtt</artifactId> |
||||
|
</dependency> |
||||
|
<dependency> |
||||
|
<groupId>io.netty</groupId> |
||||
|
<artifactId>netty-handler</artifactId> |
||||
|
</dependency> |
||||
|
<dependency> |
||||
|
<groupId>com.google.code.findbugs</groupId> |
||||
|
<artifactId>jsr305</artifactId> |
||||
|
<version>3.0.1</version> |
||||
|
<optional>true</optional> |
||||
|
</dependency> |
||||
|
<dependency> |
||||
|
<groupId>com.google.guava</groupId> |
||||
|
<artifactId>guava</artifactId> |
||||
|
</dependency> |
||||
|
</dependencies> |
||||
|
|
||||
|
<distributionManagement> |
||||
|
<repository> |
||||
|
<id>jk-5-maven</id> |
||||
|
<name>jk-5's maven server</name> |
||||
|
<url>sftp://10.2.1.2/opt/maven</url> |
||||
|
</repository> |
||||
|
</distributionManagement> |
||||
|
|
||||
|
<build> |
||||
|
<extensions> |
||||
|
<extension> |
||||
|
<groupId>org.apache.maven.wagon</groupId> |
||||
|
<artifactId>wagon-ssh</artifactId> |
||||
|
<version>2.6</version> |
||||
|
</extension> |
||||
|
</extensions> |
||||
|
<plugins> |
||||
|
<plugin> |
||||
|
<groupId>org.apache.maven.plugins</groupId> |
||||
|
<artifactId>maven-compiler-plugin</artifactId> |
||||
|
<version>3.1</version> |
||||
|
<configuration> |
||||
|
<source>1.8</source> |
||||
|
<target>1.8</target> |
||||
|
</configuration> |
||||
|
</plugin> |
||||
|
<plugin> |
||||
|
<groupId>org.apache.maven.plugins</groupId> |
||||
|
<artifactId>maven-jar-plugin</artifactId> |
||||
|
<version>2.4</version> |
||||
|
<configuration> |
||||
|
<archive> |
||||
|
<manifest> |
||||
|
<addDefaultImplementationEntries>true</addDefaultImplementationEntries> |
||||
|
</manifest> |
||||
|
</archive> |
||||
|
</configuration> |
||||
|
</plugin> |
||||
|
</plugins> |
||||
|
</build> |
||||
|
</project> |
||||
@ -0,0 +1,43 @@ |
|||||
|
/** |
||||
|
* Copyright © 2016-2018 The Thingsboard Authors |
||||
|
* |
||||
|
* 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. |
||||
|
*/ |
||||
|
package org.thingsboard.mqtt; |
||||
|
|
||||
|
/** |
||||
|
* Created by Valerii Sosliuk on 12/26/2017. |
||||
|
*/ |
||||
|
public class ChannelClosedException extends RuntimeException { |
||||
|
|
||||
|
private static final long serialVersionUID = 6266638352424706909L; |
||||
|
|
||||
|
public ChannelClosedException() { |
||||
|
} |
||||
|
|
||||
|
public ChannelClosedException(String message) { |
||||
|
super(message); |
||||
|
} |
||||
|
|
||||
|
public ChannelClosedException(String message, Throwable cause) { |
||||
|
super(message, cause); |
||||
|
} |
||||
|
|
||||
|
public ChannelClosedException(Throwable cause) { |
||||
|
super(cause); |
||||
|
} |
||||
|
|
||||
|
public ChannelClosedException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) { |
||||
|
super(message, cause, enableSuppression, writableStackTrace); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,269 @@ |
|||||
|
/** |
||||
|
* Copyright © 2016-2018 The Thingsboard Authors |
||||
|
* |
||||
|
* 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. |
||||
|
*/ |
||||
|
package org.thingsboard.mqtt; |
||||
|
|
||||
|
import com.google.common.collect.ImmutableSet; |
||||
|
import io.netty.channel.Channel; |
||||
|
import io.netty.channel.ChannelHandlerContext; |
||||
|
import io.netty.channel.SimpleChannelInboundHandler; |
||||
|
import io.netty.handler.codec.mqtt.*; |
||||
|
import io.netty.util.CharsetUtil; |
||||
|
import io.netty.util.concurrent.Promise; |
||||
|
|
||||
|
final class MqttChannelHandler extends SimpleChannelInboundHandler<MqttMessage> { |
||||
|
|
||||
|
private final MqttClientImpl client; |
||||
|
private final Promise<MqttConnectResult> connectFuture; |
||||
|
|
||||
|
MqttChannelHandler(MqttClientImpl client, Promise<MqttConnectResult> connectFuture) { |
||||
|
this.client = client; |
||||
|
this.connectFuture = connectFuture; |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
protected void channelRead0(ChannelHandlerContext ctx, MqttMessage msg) throws Exception { |
||||
|
switch (msg.fixedHeader().messageType()) { |
||||
|
case CONNACK: |
||||
|
handleConack(ctx.channel(), (MqttConnAckMessage) msg); |
||||
|
break; |
||||
|
case SUBACK: |
||||
|
handleSubAck((MqttSubAckMessage) msg); |
||||
|
break; |
||||
|
case PUBLISH: |
||||
|
handlePublish(ctx.channel(), (MqttPublishMessage) msg); |
||||
|
break; |
||||
|
case UNSUBACK: |
||||
|
handleUnsuback((MqttUnsubAckMessage) msg); |
||||
|
break; |
||||
|
case PUBACK: |
||||
|
handlePuback((MqttPubAckMessage) msg); |
||||
|
break; |
||||
|
case PUBREC: |
||||
|
handlePubrec(ctx.channel(), msg); |
||||
|
break; |
||||
|
case PUBREL: |
||||
|
handlePubrel(ctx.channel(), msg); |
||||
|
break; |
||||
|
case PUBCOMP: |
||||
|
handlePubcomp(msg); |
||||
|
break; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public void channelActive(ChannelHandlerContext ctx) throws Exception { |
||||
|
super.channelActive(ctx); |
||||
|
|
||||
|
MqttFixedHeader fixedHeader = new MqttFixedHeader(MqttMessageType.CONNECT, false, MqttQoS.AT_MOST_ONCE, false, 0); |
||||
|
MqttConnectVariableHeader variableHeader = new MqttConnectVariableHeader( |
||||
|
this.client.getClientConfig().getProtocolVersion().protocolName(), // Protocol Name
|
||||
|
this.client.getClientConfig().getProtocolVersion().protocolLevel(), // Protocol Level
|
||||
|
this.client.getClientConfig().getUsername() != null, // Has Username
|
||||
|
this.client.getClientConfig().getPassword() != null, // Has Password
|
||||
|
this.client.getClientConfig().getLastWill() != null // Will Retain
|
||||
|
&& this.client.getClientConfig().getLastWill().isRetain(), |
||||
|
this.client.getClientConfig().getLastWill() != null // Will QOS
|
||||
|
? this.client.getClientConfig().getLastWill().getQos().value() |
||||
|
: 0, |
||||
|
this.client.getClientConfig().getLastWill() != null, // Has Will
|
||||
|
this.client.getClientConfig().isCleanSession(), // Clean Session
|
||||
|
this.client.getClientConfig().getTimeoutSeconds() // Timeout
|
||||
|
); |
||||
|
MqttConnectPayload payload = new MqttConnectPayload( |
||||
|
this.client.getClientConfig().getClientId(), |
||||
|
this.client.getClientConfig().getLastWill() != null ? this.client.getClientConfig().getLastWill().getTopic() : null, |
||||
|
this.client.getClientConfig().getLastWill() != null ? this.client.getClientConfig().getLastWill().getMessage().getBytes(CharsetUtil.UTF_8) : null, |
||||
|
this.client.getClientConfig().getUsername(), |
||||
|
this.client.getClientConfig().getPassword() != null ? this.client.getClientConfig().getPassword().getBytes(CharsetUtil.UTF_8) : null |
||||
|
); |
||||
|
ctx.channel().writeAndFlush(new MqttConnectMessage(fixedHeader, variableHeader, payload)); |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public void channelInactive(ChannelHandlerContext ctx) throws Exception { |
||||
|
super.channelInactive(ctx); |
||||
|
} |
||||
|
|
||||
|
private void invokeHandlersForIncomingPublish(MqttPublishMessage message) { |
||||
|
for (MqttSubscribtion subscribtion : ImmutableSet.copyOf(this.client.getSubscriptions().values())) { |
||||
|
if (subscribtion.matches(message.variableHeader().topicName())) { |
||||
|
if (subscribtion.isOnce() && subscribtion.isCalled()) { |
||||
|
continue; |
||||
|
} |
||||
|
message.payload().markReaderIndex(); |
||||
|
subscribtion.setCalled(true); |
||||
|
subscribtion.getHandler().onMessage(message.variableHeader().topicName(), message.payload()); |
||||
|
if (subscribtion.isOnce()) { |
||||
|
this.client.off(subscribtion.getTopic(), subscribtion.getHandler()); |
||||
|
} |
||||
|
message.payload().resetReaderIndex(); |
||||
|
} |
||||
|
} |
||||
|
/*Set<MqttSubscribtion> subscribtions = ImmutableSet.copyOf(this.client.getSubscriptions().get(message.variableHeader().topicName())); |
||||
|
for (MqttSubscribtion subscribtion : subscribtions) { |
||||
|
if(subscribtion.isOnce() && subscribtion.isCalled()){ |
||||
|
continue; |
||||
|
} |
||||
|
message.payload().markReaderIndex(); |
||||
|
subscribtion.setCalled(true); |
||||
|
subscribtion.getHandler().onMessage(message.variableHeader().topicName(), message.payload()); |
||||
|
if(subscribtion.isOnce()){ |
||||
|
this.client.off(subscribtion.getTopic(), subscribtion.getHandler()); |
||||
|
} |
||||
|
message.payload().resetReaderIndex(); |
||||
|
}*/ |
||||
|
message.payload().release(); |
||||
|
} |
||||
|
|
||||
|
private void handleConack(Channel channel, MqttConnAckMessage message) { |
||||
|
switch (message.variableHeader().connectReturnCode()) { |
||||
|
case CONNECTION_ACCEPTED: |
||||
|
this.connectFuture.setSuccess(new MqttConnectResult(true, MqttConnectReturnCode.CONNECTION_ACCEPTED, channel.closeFuture())); |
||||
|
|
||||
|
this.client.getPendingSubscribtions().entrySet().stream().filter((e) -> !e.getValue().isSent()).forEach((e) -> { |
||||
|
channel.write(e.getValue().getSubscribeMessage()); |
||||
|
e.getValue().setSent(true); |
||||
|
}); |
||||
|
|
||||
|
this.client.getPendingPublishes().forEach((id, publish) -> { |
||||
|
if (publish.isSent()) return; |
||||
|
channel.write(publish.getMessage()); |
||||
|
publish.setSent(true); |
||||
|
if (publish.getQos() == MqttQoS.AT_MOST_ONCE) { |
||||
|
publish.getFuture().setSuccess(null); //We don't get an ACK for QOS 0
|
||||
|
this.client.getPendingPublishes().remove(publish.getMessageId()); |
||||
|
} |
||||
|
}); |
||||
|
channel.flush(); |
||||
|
break; |
||||
|
|
||||
|
case CONNECTION_REFUSED_BAD_USER_NAME_OR_PASSWORD: |
||||
|
case CONNECTION_REFUSED_IDENTIFIER_REJECTED: |
||||
|
case CONNECTION_REFUSED_NOT_AUTHORIZED: |
||||
|
case CONNECTION_REFUSED_SERVER_UNAVAILABLE: |
||||
|
case CONNECTION_REFUSED_UNACCEPTABLE_PROTOCOL_VERSION: |
||||
|
this.connectFuture.setSuccess(new MqttConnectResult(false, message.variableHeader().connectReturnCode(), channel.closeFuture())); |
||||
|
channel.close(); |
||||
|
// Don't start reconnect logic here
|
||||
|
break; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private void handleSubAck(MqttSubAckMessage message) { |
||||
|
MqttPendingSubscribtion pendingSubscription = this.client.getPendingSubscribtions().remove(message.variableHeader().messageId()); |
||||
|
if (pendingSubscription == null) { |
||||
|
return; |
||||
|
} |
||||
|
pendingSubscription.onSubackReceived(); |
||||
|
for (MqttPendingSubscribtion.MqttPendingHandler handler : pendingSubscription.getHandlers()) { |
||||
|
MqttSubscribtion subscribtion = new MqttSubscribtion(pendingSubscription.getTopic(), handler.getHandler(), handler.isOnce()); |
||||
|
this.client.getSubscriptions().put(pendingSubscription.getTopic(), subscribtion); |
||||
|
this.client.getHandlerToSubscribtion().put(handler.getHandler(), subscribtion); |
||||
|
} |
||||
|
this.client.getPendingSubscribeTopics().remove(pendingSubscription.getTopic()); |
||||
|
|
||||
|
this.client.getServerSubscribtions().add(pendingSubscription.getTopic()); |
||||
|
|
||||
|
if (!pendingSubscription.getFuture().isDone()) { |
||||
|
pendingSubscription.getFuture().setSuccess(null); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private void handlePublish(Channel channel, MqttPublishMessage message) { |
||||
|
switch (message.fixedHeader().qosLevel()) { |
||||
|
case AT_MOST_ONCE: |
||||
|
invokeHandlersForIncomingPublish(message); |
||||
|
break; |
||||
|
|
||||
|
case AT_LEAST_ONCE: |
||||
|
invokeHandlersForIncomingPublish(message); |
||||
|
if (message.variableHeader().messageId() != -1) { |
||||
|
MqttFixedHeader fixedHeader = new MqttFixedHeader(MqttMessageType.PUBACK, false, MqttQoS.AT_MOST_ONCE, false, 0); |
||||
|
MqttMessageIdVariableHeader variableHeader = MqttMessageIdVariableHeader.from(message.variableHeader().messageId()); |
||||
|
channel.writeAndFlush(new MqttPubAckMessage(fixedHeader, variableHeader)); |
||||
|
} |
||||
|
break; |
||||
|
|
||||
|
case EXACTLY_ONCE: |
||||
|
if (message.variableHeader().messageId() != -1) { |
||||
|
MqttFixedHeader fixedHeader = new MqttFixedHeader(MqttMessageType.PUBREC, false, MqttQoS.AT_MOST_ONCE, false, 0); |
||||
|
MqttMessageIdVariableHeader variableHeader = MqttMessageIdVariableHeader.from(message.variableHeader().messageId()); |
||||
|
MqttMessage pubrecMessage = new MqttMessage(fixedHeader, variableHeader); |
||||
|
|
||||
|
MqttIncomingQos2Publish incomingQos2Publish = new MqttIncomingQos2Publish(message, pubrecMessage); |
||||
|
this.client.getQos2PendingIncomingPublishes().put(message.variableHeader().messageId(), incomingQos2Publish); |
||||
|
message.payload().retain(); |
||||
|
incomingQos2Publish.startPubrecRetransmitTimer(this.client.getEventLoop().next(), this.client::sendAndFlushPacket); |
||||
|
|
||||
|
channel.writeAndFlush(pubrecMessage); |
||||
|
} |
||||
|
break; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private void handleUnsuback(MqttUnsubAckMessage message) { |
||||
|
MqttPendingUnsubscribtion unsubscribtion = this.client.getPendingServerUnsubscribes().get(message.variableHeader().messageId()); |
||||
|
if (unsubscribtion == null) { |
||||
|
return; |
||||
|
} |
||||
|
unsubscribtion.onUnsubackReceived(); |
||||
|
this.client.getServerSubscribtions().remove(unsubscribtion.getTopic()); |
||||
|
unsubscribtion.getFuture().setSuccess(null); |
||||
|
this.client.getPendingServerUnsubscribes().remove(message.variableHeader().messageId()); |
||||
|
} |
||||
|
|
||||
|
private void handlePuback(MqttPubAckMessage message) { |
||||
|
MqttPendingPublish pendingPublish = this.client.getPendingPublishes().get(message.variableHeader().messageId()); |
||||
|
pendingPublish.getFuture().setSuccess(null); |
||||
|
pendingPublish.onPubackReceived(); |
||||
|
this.client.getPendingPublishes().remove(message.variableHeader().messageId()); |
||||
|
pendingPublish.getPayload().release(); |
||||
|
} |
||||
|
|
||||
|
private void handlePubrec(Channel channel, MqttMessage message) { |
||||
|
MqttPendingPublish pendingPublish = this.client.getPendingPublishes().get(((MqttMessageIdVariableHeader) message.variableHeader()).messageId()); |
||||
|
pendingPublish.onPubackReceived(); |
||||
|
|
||||
|
MqttFixedHeader fixedHeader = new MqttFixedHeader(MqttMessageType.PUBREL, false, MqttQoS.AT_LEAST_ONCE, false, 0); |
||||
|
MqttMessageIdVariableHeader variableHeader = (MqttMessageIdVariableHeader) message.variableHeader(); |
||||
|
MqttMessage pubrelMessage = new MqttMessage(fixedHeader, variableHeader); |
||||
|
channel.writeAndFlush(pubrelMessage); |
||||
|
|
||||
|
pendingPublish.setPubrelMessage(pubrelMessage); |
||||
|
pendingPublish.startPubrelRetransmissionTimer(this.client.getEventLoop().next(), this.client::sendAndFlushPacket); |
||||
|
} |
||||
|
|
||||
|
private void handlePubrel(Channel channel, MqttMessage message) { |
||||
|
if (this.client.getQos2PendingIncomingPublishes().containsKey(((MqttMessageIdVariableHeader) message.variableHeader()).messageId())) { |
||||
|
MqttIncomingQos2Publish incomingQos2Publish = this.client.getQos2PendingIncomingPublishes().get(((MqttMessageIdVariableHeader) message.variableHeader()).messageId()); |
||||
|
this.invokeHandlersForIncomingPublish(incomingQos2Publish.getIncomingPublish()); |
||||
|
incomingQos2Publish.onPubrelReceived(); |
||||
|
this.client.getQos2PendingIncomingPublishes().remove(incomingQos2Publish.getIncomingPublish().variableHeader().messageId()); |
||||
|
} |
||||
|
MqttFixedHeader fixedHeader = new MqttFixedHeader(MqttMessageType.PUBCOMP, false, MqttQoS.AT_MOST_ONCE, false, 0); |
||||
|
MqttMessageIdVariableHeader variableHeader = MqttMessageIdVariableHeader.from(((MqttMessageIdVariableHeader) message.variableHeader()).messageId()); |
||||
|
channel.writeAndFlush(new MqttMessage(fixedHeader, variableHeader)); |
||||
|
} |
||||
|
|
||||
|
private void handlePubcomp(MqttMessage message) { |
||||
|
MqttMessageIdVariableHeader variableHeader = (MqttMessageIdVariableHeader) message.variableHeader(); |
||||
|
MqttPendingPublish pendingPublish = this.client.getPendingPublishes().get(variableHeader.messageId()); |
||||
|
pendingPublish.getFuture().setSuccess(null); |
||||
|
this.client.getPendingPublishes().remove(variableHeader.messageId()); |
||||
|
pendingPublish.getPayload().release(); |
||||
|
pendingPublish.onPubcompReceived(); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,205 @@ |
|||||
|
/** |
||||
|
* Copyright © 2016-2018 The Thingsboard Authors |
||||
|
* |
||||
|
* 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. |
||||
|
*/ |
||||
|
package org.thingsboard.mqtt; |
||||
|
|
||||
|
import io.netty.buffer.ByteBuf; |
||||
|
import io.netty.channel.Channel; |
||||
|
import io.netty.channel.EventLoopGroup; |
||||
|
import io.netty.channel.nio.NioEventLoopGroup; |
||||
|
import io.netty.handler.codec.mqtt.MqttQoS; |
||||
|
import io.netty.util.concurrent.Future; |
||||
|
|
||||
|
public interface MqttClient { |
||||
|
|
||||
|
/** |
||||
|
* Connect to the specified hostname/ip. By default uses port 1883. |
||||
|
* If you want to change the port number, see {@link #connect(String, int)} |
||||
|
* |
||||
|
* @param host The ip address or host to connect to |
||||
|
* @return A future which will be completed when the connection is opened and we received an CONNACK |
||||
|
*/ |
||||
|
Future<MqttConnectResult> connect(String host); |
||||
|
|
||||
|
/** |
||||
|
* Connect to the specified hostname/ip using the specified port |
||||
|
* |
||||
|
* @param host The ip address or host to connect to |
||||
|
* @param port The tcp port to connect to |
||||
|
* @return A future which will be completed when the connection is opened and we received an CONNACK |
||||
|
*/ |
||||
|
Future<MqttConnectResult> connect(String host, int port); |
||||
|
|
||||
|
/** |
||||
|
* |
||||
|
* @return boolean value indicating if channel is active |
||||
|
*/ |
||||
|
boolean isConnected(); |
||||
|
|
||||
|
/** |
||||
|
* Attempt reconnect to the host that was attempted with {@link #connect(String, int)} method before |
||||
|
* |
||||
|
* @return A future which will be completed when the connection is opened and we received an CONNACK |
||||
|
* @throws IllegalStateException if no previous {@link #connect(String, int)} calls were attempted |
||||
|
*/ |
||||
|
Future<MqttConnectResult> reconnect(); |
||||
|
|
||||
|
/** |
||||
|
* Retrieve the netty {@link EventLoopGroup} we are using |
||||
|
* @return The netty {@link EventLoopGroup} we use for the connection |
||||
|
*/ |
||||
|
EventLoopGroup getEventLoop(); |
||||
|
|
||||
|
/** |
||||
|
* By default we use the netty {@link NioEventLoopGroup}. |
||||
|
* If you change the EventLoopGroup to another type, make sure to change the {@link Channel} class using {@link MqttClientConfig#setChannelClass(Class)} |
||||
|
* If you want to force the MqttClient to use another {@link EventLoopGroup}, call this function before calling {@link #connect(String, int)} |
||||
|
* |
||||
|
* @param eventLoop The new eventloop to use |
||||
|
*/ |
||||
|
void setEventLoop(EventLoopGroup eventLoop); |
||||
|
|
||||
|
/** |
||||
|
* Subscribe on the given topic. When a message is received, MqttClient will invoke the {@link MqttHandler#onMessage(String, ByteBuf)} function of the given handler |
||||
|
* |
||||
|
* @param topic The topic filter to subscribe to |
||||
|
* @param handler The handler to invoke when we receive a message |
||||
|
* @return A future which will be completed when the server acknowledges our subscribe request |
||||
|
*/ |
||||
|
Future<Void> on(String topic, MqttHandler handler); |
||||
|
|
||||
|
/** |
||||
|
* Subscribe on the given topic, with the given qos. When a message is received, MqttClient will invoke the {@link MqttHandler#onMessage(String, ByteBuf)} function of the given handler |
||||
|
* |
||||
|
* @param topic The topic filter to subscribe to |
||||
|
* @param handler The handler to invoke when we receive a message |
||||
|
* @param qos The qos to request to the server |
||||
|
* @return A future which will be completed when the server acknowledges our subscribe request |
||||
|
*/ |
||||
|
Future<Void> on(String topic, MqttHandler handler, MqttQoS qos); |
||||
|
|
||||
|
/** |
||||
|
* Subscribe on the given topic. When a message is received, MqttClient will invoke the {@link MqttHandler#onMessage(String, ByteBuf)} function of the given handler |
||||
|
* This subscribtion is only once. If the MqttClient has received 1 message, the subscribtion will be removed |
||||
|
* |
||||
|
* @param topic The topic filter to subscribe to |
||||
|
* @param handler The handler to invoke when we receive a message |
||||
|
* @return A future which will be completed when the server acknowledges our subscribe request |
||||
|
*/ |
||||
|
Future<Void> once(String topic, MqttHandler handler); |
||||
|
|
||||
|
/** |
||||
|
* Subscribe on the given topic, with the given qos. When a message is received, MqttClient will invoke the {@link MqttHandler#onMessage(String, ByteBuf)} function of the given handler |
||||
|
* This subscribtion is only once. If the MqttClient has received 1 message, the subscribtion will be removed |
||||
|
* |
||||
|
* @param topic The topic filter to subscribe to |
||||
|
* @param handler The handler to invoke when we receive a message |
||||
|
* @param qos The qos to request to the server |
||||
|
* @return A future which will be completed when the server acknowledges our subscribe request |
||||
|
*/ |
||||
|
Future<Void> once(String topic, MqttHandler handler, MqttQoS qos); |
||||
|
|
||||
|
/** |
||||
|
* Remove the subscribtion for the given topic and handler |
||||
|
* If you want to unsubscribe from all handlers known for this topic, use {@link #off(String)} |
||||
|
* |
||||
|
* @param topic The topic to unsubscribe for |
||||
|
* @param handler The handler to unsubscribe |
||||
|
* @return A future which will be completed when the server acknowledges our unsubscribe request |
||||
|
*/ |
||||
|
Future<Void> off(String topic, MqttHandler handler); |
||||
|
|
||||
|
/** |
||||
|
* Remove all subscribtions for the given topic. |
||||
|
* If you want to specify which handler to unsubscribe, use {@link #off(String, MqttHandler)} |
||||
|
* |
||||
|
* @param topic The topic to unsubscribe for |
||||
|
* @return A future which will be completed when the server acknowledges our unsubscribe request |
||||
|
*/ |
||||
|
Future<Void> off(String topic); |
||||
|
|
||||
|
/** |
||||
|
* Publish a message to the given payload |
||||
|
* @param topic The topic to publish to |
||||
|
* @param payload The payload to send |
||||
|
* @return A future which will be completed when the message is sent out of the MqttClient |
||||
|
*/ |
||||
|
Future<Void> publish(String topic, ByteBuf payload); |
||||
|
|
||||
|
/** |
||||
|
* Publish a message to the given payload, using the given qos |
||||
|
* @param topic The topic to publish to |
||||
|
* @param payload The payload to send |
||||
|
* @param qos The qos to use while publishing |
||||
|
* @return A future which will be completed when the message is delivered to the server |
||||
|
*/ |
||||
|
Future<Void> publish(String topic, ByteBuf payload, MqttQoS qos); |
||||
|
|
||||
|
/** |
||||
|
* Publish a message to the given payload, using optional retain |
||||
|
* @param topic The topic to publish to |
||||
|
* @param payload The payload to send |
||||
|
* @param retain true if you want to retain the message on the server, false otherwise |
||||
|
* @return A future which will be completed when the message is sent out of the MqttClient |
||||
|
*/ |
||||
|
Future<Void> publish(String topic, ByteBuf payload, boolean retain); |
||||
|
|
||||
|
/** |
||||
|
* Publish a message to the given payload, using the given qos and optional retain |
||||
|
* @param topic The topic to publish to |
||||
|
* @param payload The payload to send |
||||
|
* @param qos The qos to use while publishing |
||||
|
* @param retain true if you want to retain the message on the server, false otherwise |
||||
|
* @return A future which will be completed when the message is delivered to the server |
||||
|
*/ |
||||
|
Future<Void> publish(String topic, ByteBuf payload, MqttQoS qos, boolean retain); |
||||
|
|
||||
|
/** |
||||
|
* Retrieve the MqttClient configuration |
||||
|
* @return The {@link MqttClientConfig} instance we use |
||||
|
*/ |
||||
|
MqttClientConfig getClientConfig(); |
||||
|
|
||||
|
/** |
||||
|
* Construct the MqttClientImpl with default config |
||||
|
*/ |
||||
|
static MqttClient create(){ |
||||
|
return new MqttClientImpl(); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Construct the MqttClientImpl with additional config. |
||||
|
* This config can also be changed using the {@link #getClientConfig()} function |
||||
|
* |
||||
|
* @param config The config object to use while looking for settings |
||||
|
*/ |
||||
|
static MqttClient create(MqttClientConfig config){ |
||||
|
return new MqttClientImpl(config); |
||||
|
} |
||||
|
|
||||
|
|
||||
|
/** |
||||
|
* Send disconnect and close channel |
||||
|
* |
||||
|
*/ |
||||
|
void disconnect(); |
||||
|
|
||||
|
/** |
||||
|
* Sets the {@see #MqttClientCallback} object for this MqttClient |
||||
|
* @param callback The callback to be set |
||||
|
*/ |
||||
|
void setCallback(MqttClientCallback callback); |
||||
|
|
||||
|
} |
||||
@ -0,0 +1,29 @@ |
|||||
|
/** |
||||
|
* Copyright © 2016-2018 The Thingsboard Authors |
||||
|
* |
||||
|
* 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. |
||||
|
*/ |
||||
|
package org.thingsboard.mqtt; |
||||
|
|
||||
|
/** |
||||
|
* Created by Valerii Sosliuk on 12/30/2017. |
||||
|
*/ |
||||
|
public interface MqttClientCallback { |
||||
|
|
||||
|
/** |
||||
|
* This method is called when the connection to the server is lost. |
||||
|
* |
||||
|
* @param cause the reason behind the loss of connection. |
||||
|
*/ |
||||
|
public void connectionLost(Throwable cause); |
||||
|
} |
||||
@ -0,0 +1,149 @@ |
|||||
|
/** |
||||
|
* Copyright © 2016-2018 The Thingsboard Authors |
||||
|
* |
||||
|
* 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. |
||||
|
*/ |
||||
|
package org.thingsboard.mqtt; |
||||
|
|
||||
|
import io.netty.channel.Channel; |
||||
|
import io.netty.channel.socket.nio.NioSocketChannel; |
||||
|
import io.netty.handler.codec.mqtt.MqttVersion; |
||||
|
import io.netty.handler.ssl.SslContext; |
||||
|
|
||||
|
import javax.annotation.Nonnull; |
||||
|
import javax.annotation.Nullable; |
||||
|
import java.util.Random; |
||||
|
|
||||
|
@SuppressWarnings({"WeakerAccess", "unused"}) |
||||
|
public final class MqttClientConfig { |
||||
|
|
||||
|
private final SslContext sslContext; |
||||
|
private final String randomClientId; |
||||
|
|
||||
|
private String clientId; |
||||
|
private int timeoutSeconds = 60; |
||||
|
private MqttVersion protocolVersion = MqttVersion.MQTT_3_1; |
||||
|
@Nullable private String username = null; |
||||
|
@Nullable private String password = null; |
||||
|
private boolean cleanSession = true; |
||||
|
@Nullable private MqttLastWill lastWill; |
||||
|
private Class<? extends Channel> channelClass = NioSocketChannel.class; |
||||
|
|
||||
|
private boolean reconnect = true; |
||||
|
|
||||
|
public MqttClientConfig() { |
||||
|
this(null); |
||||
|
} |
||||
|
|
||||
|
public MqttClientConfig(SslContext sslContext) { |
||||
|
this.sslContext = sslContext; |
||||
|
Random random = new Random(); |
||||
|
String id = "netty-mqtt/"; |
||||
|
String[] options = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789".split(""); |
||||
|
for(int i = 0; i < 8; i++){ |
||||
|
id += options[random.nextInt(options.length)]; |
||||
|
} |
||||
|
this.clientId = id; |
||||
|
this.randomClientId = id; |
||||
|
} |
||||
|
|
||||
|
@Nonnull |
||||
|
public String getClientId() { |
||||
|
return clientId; |
||||
|
} |
||||
|
|
||||
|
public void setClientId(@Nullable String clientId) { |
||||
|
if(clientId == null){ |
||||
|
this.clientId = randomClientId; |
||||
|
}else{ |
||||
|
this.clientId = clientId; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public int getTimeoutSeconds() { |
||||
|
return timeoutSeconds; |
||||
|
} |
||||
|
|
||||
|
public void setTimeoutSeconds(int timeoutSeconds) { |
||||
|
if(timeoutSeconds != -1 && timeoutSeconds <= 0){ |
||||
|
throw new IllegalArgumentException("timeoutSeconds must be > 0 or -1"); |
||||
|
} |
||||
|
this.timeoutSeconds = timeoutSeconds; |
||||
|
} |
||||
|
|
||||
|
public MqttVersion getProtocolVersion() { |
||||
|
return protocolVersion; |
||||
|
} |
||||
|
|
||||
|
public void setProtocolVersion(MqttVersion protocolVersion) { |
||||
|
if(protocolVersion == null){ |
||||
|
throw new NullPointerException("protocolVersion"); |
||||
|
} |
||||
|
this.protocolVersion = protocolVersion; |
||||
|
} |
||||
|
|
||||
|
@Nullable |
||||
|
public String getUsername() { |
||||
|
return username; |
||||
|
} |
||||
|
|
||||
|
public void setUsername(@Nullable String username) { |
||||
|
this.username = username; |
||||
|
} |
||||
|
|
||||
|
@Nullable |
||||
|
public String getPassword() { |
||||
|
return password; |
||||
|
} |
||||
|
|
||||
|
public void setPassword(@Nullable String password) { |
||||
|
this.password = password; |
||||
|
} |
||||
|
|
||||
|
public boolean isCleanSession() { |
||||
|
return cleanSession; |
||||
|
} |
||||
|
|
||||
|
public void setCleanSession(boolean cleanSession) { |
||||
|
this.cleanSession = cleanSession; |
||||
|
} |
||||
|
|
||||
|
@Nullable |
||||
|
public MqttLastWill getLastWill() { |
||||
|
return lastWill; |
||||
|
} |
||||
|
|
||||
|
public void setLastWill(@Nullable MqttLastWill lastWill) { |
||||
|
this.lastWill = lastWill; |
||||
|
} |
||||
|
|
||||
|
public Class<? extends Channel> getChannelClass() { |
||||
|
return channelClass; |
||||
|
} |
||||
|
|
||||
|
public void setChannelClass(Class<? extends Channel> channelClass) { |
||||
|
this.channelClass = channelClass; |
||||
|
} |
||||
|
|
||||
|
public SslContext getSslContext() { |
||||
|
return sslContext; |
||||
|
} |
||||
|
|
||||
|
public boolean isReconnect() { |
||||
|
return reconnect; |
||||
|
} |
||||
|
|
||||
|
public void setReconnect(boolean reconnect) { |
||||
|
this.reconnect = reconnect; |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,484 @@ |
|||||
|
/** |
||||
|
* Copyright © 2016-2018 The Thingsboard Authors |
||||
|
* |
||||
|
* 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. |
||||
|
*/ |
||||
|
package org.thingsboard.mqtt; |
||||
|
|
||||
|
import com.google.common.collect.HashMultimap; |
||||
|
import com.google.common.collect.ImmutableSet; |
||||
|
import io.netty.bootstrap.Bootstrap; |
||||
|
import io.netty.buffer.ByteBuf; |
||||
|
import io.netty.channel.*; |
||||
|
import io.netty.channel.nio.NioEventLoopGroup; |
||||
|
import io.netty.channel.socket.SocketChannel; |
||||
|
import io.netty.handler.codec.mqtt.*; |
||||
|
import io.netty.handler.ssl.SslContext; |
||||
|
import io.netty.handler.timeout.IdleStateHandler; |
||||
|
import io.netty.util.collection.IntObjectHashMap; |
||||
|
import io.netty.util.concurrent.DefaultPromise; |
||||
|
import io.netty.util.concurrent.Future; |
||||
|
import io.netty.util.concurrent.Promise; |
||||
|
|
||||
|
import java.util.*; |
||||
|
import java.util.concurrent.TimeUnit; |
||||
|
import java.util.concurrent.atomic.AtomicInteger; |
||||
|
|
||||
|
/** |
||||
|
* Represents an MqttClientImpl connected to a single MQTT server. Will try to keep the connection going at all times |
||||
|
*/ |
||||
|
@SuppressWarnings({"WeakerAccess", "unused"}) |
||||
|
final class MqttClientImpl implements MqttClient { |
||||
|
|
||||
|
private final Set<String> serverSubscribtions = new HashSet<>(); |
||||
|
private final IntObjectHashMap<MqttPendingUnsubscribtion> pendingServerUnsubscribes = new IntObjectHashMap<>(); |
||||
|
private final IntObjectHashMap<MqttIncomingQos2Publish> qos2PendingIncomingPublishes = new IntObjectHashMap<>(); |
||||
|
private final IntObjectHashMap<MqttPendingPublish> pendingPublishes = new IntObjectHashMap<>(); |
||||
|
private final HashMultimap<String, MqttSubscribtion> subscriptions = HashMultimap.create(); |
||||
|
private final IntObjectHashMap<MqttPendingSubscribtion> pendingSubscribtions = new IntObjectHashMap<>(); |
||||
|
private final Set<String> pendingSubscribeTopics = new HashSet<>(); |
||||
|
private final HashMultimap<MqttHandler, MqttSubscribtion> handlerToSubscribtion = HashMultimap.create(); |
||||
|
private final AtomicInteger nextMessageId = new AtomicInteger(1); |
||||
|
|
||||
|
private final MqttClientConfig clientConfig; |
||||
|
|
||||
|
private EventLoopGroup eventLoop; |
||||
|
|
||||
|
private Channel channel; |
||||
|
|
||||
|
private boolean disconnected = false; |
||||
|
private String host; |
||||
|
private int port; |
||||
|
private MqttClientCallback callback; |
||||
|
|
||||
|
|
||||
|
/** |
||||
|
* Construct the MqttClientImpl with default config |
||||
|
*/ |
||||
|
public MqttClientImpl() { |
||||
|
this.clientConfig = new MqttClientConfig(); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Construct the MqttClientImpl with additional config. |
||||
|
* This config can also be changed using the {@link #getClientConfig()} function |
||||
|
* |
||||
|
* @param clientConfig The config object to use while looking for settings |
||||
|
*/ |
||||
|
public MqttClientImpl(MqttClientConfig clientConfig) { |
||||
|
this.clientConfig = clientConfig; |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Connect to the specified hostname/ip. By default uses port 1883. |
||||
|
* If you want to change the port number, see {@link #connect(String, int)} |
||||
|
* |
||||
|
* @param host The ip address or host to connect to |
||||
|
* @return A future which will be completed when the connection is opened and we received an CONNACK |
||||
|
*/ |
||||
|
@Override |
||||
|
public Future<MqttConnectResult> connect(String host) { |
||||
|
return connect(host, 1883); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Connect to the specified hostname/ip using the specified port |
||||
|
* |
||||
|
* @param host The ip address or host to connect to |
||||
|
* @param port The tcp port to connect to |
||||
|
* @return A future which will be completed when the connection is opened and we received an CONNACK |
||||
|
*/ |
||||
|
@Override |
||||
|
public Future<MqttConnectResult> connect(String host, int port) { |
||||
|
if (this.eventLoop == null) { |
||||
|
this.eventLoop = new NioEventLoopGroup(); |
||||
|
} |
||||
|
this.host = host; |
||||
|
this.port = port; |
||||
|
|
||||
|
Promise<MqttConnectResult> connectFuture = new DefaultPromise<>(this.eventLoop.next()); |
||||
|
Bootstrap bootstrap = new Bootstrap(); |
||||
|
bootstrap.group(this.eventLoop); |
||||
|
bootstrap.channel(clientConfig.getChannelClass()); |
||||
|
bootstrap.remoteAddress(host, port); |
||||
|
bootstrap.handler(new MqttChannelInitializer(connectFuture, host, port, clientConfig.getSslContext())); |
||||
|
ChannelFuture future = bootstrap.connect(); |
||||
|
future.addListener((ChannelFutureListener) f -> { |
||||
|
if (f.isSuccess()) { |
||||
|
MqttClientImpl.this.channel = f.channel(); |
||||
|
} else if (clientConfig.isReconnect() && !disconnected) { |
||||
|
eventLoop.schedule((Runnable) () -> connect(host, port), 1L, TimeUnit.SECONDS); |
||||
|
} |
||||
|
}); |
||||
|
return connectFuture; |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public boolean isConnected() { |
||||
|
if (!disconnected) { |
||||
|
return channel == null ? false : channel.isActive(); |
||||
|
}; |
||||
|
return false; |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public Future<MqttConnectResult> reconnect() { |
||||
|
if (host == null) { |
||||
|
throw new IllegalStateException("Cannot reconnect. Call connect() first"); |
||||
|
} |
||||
|
return connect(host, port); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Retrieve the netty {@link EventLoopGroup} we are using |
||||
|
* |
||||
|
* @return The netty {@link EventLoopGroup} we use for the connection |
||||
|
*/ |
||||
|
@Override |
||||
|
public EventLoopGroup getEventLoop() { |
||||
|
return eventLoop; |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* By default we use the netty {@link NioEventLoopGroup}. |
||||
|
* If you change the EventLoopGroup to another type, make sure to change the {@link Channel} class using {@link MqttClientConfig#setChannelClass(Class)} |
||||
|
* If you want to force the MqttClient to use another {@link EventLoopGroup}, call this function before calling {@link #connect(String, int)} |
||||
|
* |
||||
|
* @param eventLoop The new eventloop to use |
||||
|
*/ |
||||
|
@Override |
||||
|
public void setEventLoop(EventLoopGroup eventLoop) { |
||||
|
this.eventLoop = eventLoop; |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Subscribe on the given topic. When a message is received, MqttClient will invoke the {@link MqttHandler#onMessage(String, ByteBuf)} function of the given handler |
||||
|
* |
||||
|
* @param topic The topic filter to subscribe to |
||||
|
* @param handler The handler to invoke when we receive a message |
||||
|
* @return A future which will be completed when the server acknowledges our subscribe request |
||||
|
*/ |
||||
|
@Override |
||||
|
public Future<Void> on(String topic, MqttHandler handler) { |
||||
|
return on(topic, handler, MqttQoS.AT_MOST_ONCE); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Subscribe on the given topic, with the given qos. When a message is received, MqttClient will invoke the {@link MqttHandler#onMessage(String, ByteBuf)} function of the given handler |
||||
|
* |
||||
|
* @param topic The topic filter to subscribe to |
||||
|
* @param handler The handler to invoke when we receive a message |
||||
|
* @param qos The qos to request to the server |
||||
|
* @return A future which will be completed when the server acknowledges our subscribe request |
||||
|
*/ |
||||
|
@Override |
||||
|
public Future<Void> on(String topic, MqttHandler handler, MqttQoS qos) { |
||||
|
return createSubscribtion(topic, handler, false, qos); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Subscribe on the given topic. When a message is received, MqttClient will invoke the {@link MqttHandler#onMessage(String, ByteBuf)} function of the given handler |
||||
|
* This subscribtion is only once. If the MqttClient has received 1 message, the subscribtion will be removed |
||||
|
* |
||||
|
* @param topic The topic filter to subscribe to |
||||
|
* @param handler The handler to invoke when we receive a message |
||||
|
* @return A future which will be completed when the server acknowledges our subscribe request |
||||
|
*/ |
||||
|
@Override |
||||
|
public Future<Void> once(String topic, MqttHandler handler) { |
||||
|
return once(topic, handler, MqttQoS.AT_MOST_ONCE); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Subscribe on the given topic, with the given qos. When a message is received, MqttClient will invoke the {@link MqttHandler#onMessage(String, ByteBuf)} function of the given handler |
||||
|
* This subscribtion is only once. If the MqttClient has received 1 message, the subscribtion will be removed |
||||
|
* |
||||
|
* @param topic The topic filter to subscribe to |
||||
|
* @param handler The handler to invoke when we receive a message |
||||
|
* @param qos The qos to request to the server |
||||
|
* @return A future which will be completed when the server acknowledges our subscribe request |
||||
|
*/ |
||||
|
@Override |
||||
|
public Future<Void> once(String topic, MqttHandler handler, MqttQoS qos) { |
||||
|
return createSubscribtion(topic, handler, true, qos); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Remove the subscribtion for the given topic and handler |
||||
|
* If you want to unsubscribe from all handlers known for this topic, use {@link #off(String)} |
||||
|
* |
||||
|
* @param topic The topic to unsubscribe for |
||||
|
* @param handler The handler to unsubscribe |
||||
|
* @return A future which will be completed when the server acknowledges our unsubscribe request |
||||
|
*/ |
||||
|
@Override |
||||
|
public Future<Void> off(String topic, MqttHandler handler) { |
||||
|
Promise<Void> future = new DefaultPromise<>(this.eventLoop.next()); |
||||
|
for (MqttSubscribtion subscribtion : this.handlerToSubscribtion.get(handler)) { |
||||
|
this.subscriptions.remove(topic, subscribtion); |
||||
|
} |
||||
|
this.handlerToSubscribtion.removeAll(handler); |
||||
|
this.checkSubscribtions(topic, future); |
||||
|
return future; |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Remove all subscribtions for the given topic. |
||||
|
* If you want to specify which handler to unsubscribe, use {@link #off(String, MqttHandler)} |
||||
|
* |
||||
|
* @param topic The topic to unsubscribe for |
||||
|
* @return A future which will be completed when the server acknowledges our unsubscribe request |
||||
|
*/ |
||||
|
@Override |
||||
|
public Future<Void> off(String topic) { |
||||
|
Promise<Void> future = new DefaultPromise<>(this.eventLoop.next()); |
||||
|
ImmutableSet<MqttSubscribtion> subscribtions = ImmutableSet.copyOf(this.subscriptions.get(topic)); |
||||
|
for (MqttSubscribtion subscribtion : subscribtions) { |
||||
|
for (MqttSubscribtion handSub : this.handlerToSubscribtion.get(subscribtion.getHandler())) { |
||||
|
this.subscriptions.remove(topic, handSub); |
||||
|
} |
||||
|
this.handlerToSubscribtion.remove(subscribtion.getHandler(), subscribtion); |
||||
|
} |
||||
|
this.checkSubscribtions(topic, future); |
||||
|
return future; |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Publish a message to the given payload |
||||
|
* |
||||
|
* @param topic The topic to publish to |
||||
|
* @param payload The payload to send |
||||
|
* @return A future which will be completed when the message is sent out of the MqttClient |
||||
|
*/ |
||||
|
@Override |
||||
|
public Future<Void> publish(String topic, ByteBuf payload) { |
||||
|
return publish(topic, payload, MqttQoS.AT_MOST_ONCE, false); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Publish a message to the given payload, using the given qos |
||||
|
* |
||||
|
* @param topic The topic to publish to |
||||
|
* @param payload The payload to send |
||||
|
* @param qos The qos to use while publishing |
||||
|
* @return A future which will be completed when the message is delivered to the server |
||||
|
*/ |
||||
|
@Override |
||||
|
public Future<Void> publish(String topic, ByteBuf payload, MqttQoS qos) { |
||||
|
return publish(topic, payload, qos, false); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Publish a message to the given payload, using optional retain |
||||
|
* |
||||
|
* @param topic The topic to publish to |
||||
|
* @param payload The payload to send |
||||
|
* @param retain true if you want to retain the message on the server, false otherwise |
||||
|
* @return A future which will be completed when the message is sent out of the MqttClient |
||||
|
*/ |
||||
|
@Override |
||||
|
public Future<Void> publish(String topic, ByteBuf payload, boolean retain) { |
||||
|
return publish(topic, payload, MqttQoS.AT_MOST_ONCE, retain); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Publish a message to the given payload, using the given qos and optional retain |
||||
|
* |
||||
|
* @param topic The topic to publish to |
||||
|
* @param payload The payload to send |
||||
|
* @param qos The qos to use while publishing |
||||
|
* @param retain true if you want to retain the message on the server, false otherwise |
||||
|
* @return A future which will be completed when the message is delivered to the server |
||||
|
*/ |
||||
|
@Override |
||||
|
public Future<Void> publish(String topic, ByteBuf payload, MqttQoS qos, boolean retain) { |
||||
|
Promise<Void> future = new DefaultPromise<>(this.eventLoop.next()); |
||||
|
MqttFixedHeader fixedHeader = new MqttFixedHeader(MqttMessageType.PUBLISH, false, qos, retain, 0); |
||||
|
MqttPublishVariableHeader variableHeader = new MqttPublishVariableHeader(topic, getNewMessageId().messageId()); |
||||
|
MqttPublishMessage message = new MqttPublishMessage(fixedHeader, variableHeader, payload); |
||||
|
MqttPendingPublish pendingPublish = new MqttPendingPublish(variableHeader.messageId(), future, payload.retain(), message, qos); |
||||
|
ChannelFuture channelFuture = this.sendAndFlushPacket(message); |
||||
|
|
||||
|
if (channelFuture != null) { |
||||
|
pendingPublish.setSent(channelFuture != null); |
||||
|
if (channelFuture.cause() != null) { |
||||
|
future.setFailure(channelFuture.cause()); |
||||
|
return future; |
||||
|
} |
||||
|
} |
||||
|
if (pendingPublish.isSent() && pendingPublish.getQos() == MqttQoS.AT_MOST_ONCE) { |
||||
|
pendingPublish.getFuture().setSuccess(null); //We don't get an ACK for QOS 0
|
||||
|
} else if (pendingPublish.isSent()) { |
||||
|
this.pendingPublishes.put(pendingPublish.getMessageId(), pendingPublish); |
||||
|
pendingPublish.startPublishRetransmissionTimer(this.eventLoop.next(), this::sendAndFlushPacket); |
||||
|
} |
||||
|
return future; |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Retrieve the MqttClient configuration |
||||
|
* |
||||
|
* @return The {@link MqttClientConfig} instance we use |
||||
|
*/ |
||||
|
@Override |
||||
|
public MqttClientConfig getClientConfig() { |
||||
|
return clientConfig; |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public void disconnect() { |
||||
|
disconnected = true; |
||||
|
if (this.channel != null) { |
||||
|
MqttMessage message = new MqttMessage(new MqttFixedHeader(MqttMessageType.DISCONNECT, false, MqttQoS.AT_MOST_ONCE, false, 0)); |
||||
|
this.sendAndFlushPacket(message).addListener(future1 -> channel.close()); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public void setCallback(MqttClientCallback callback) { |
||||
|
this.callback = callback; |
||||
|
} |
||||
|
|
||||
|
|
||||
|
///////////////////////////////////////////// PRIVATE API /////////////////////////////////////////////
|
||||
|
|
||||
|
ChannelFuture sendAndFlushPacket(Object message) { |
||||
|
if (this.channel == null) { |
||||
|
return null; |
||||
|
} |
||||
|
if (this.channel.isActive()) { |
||||
|
return this.channel.writeAndFlush(message); |
||||
|
} |
||||
|
ChannelClosedException e = new ChannelClosedException("Channel is closed"); |
||||
|
if (callback != null) { |
||||
|
callback.connectionLost(e); |
||||
|
} |
||||
|
return this.channel.newFailedFuture(e); |
||||
|
} |
||||
|
|
||||
|
private MqttMessageIdVariableHeader getNewMessageId() { |
||||
|
this.nextMessageId.compareAndSet(0xffff, 1); |
||||
|
return MqttMessageIdVariableHeader.from(this.nextMessageId.getAndIncrement()); |
||||
|
} |
||||
|
|
||||
|
private Future<Void> createSubscribtion(String topic, MqttHandler handler, boolean once, MqttQoS qos) { |
||||
|
if (this.pendingSubscribeTopics.contains(topic)) { |
||||
|
Optional<Map.Entry<Integer, MqttPendingSubscribtion>> subscribtionEntry = this.pendingSubscribtions.entrySet().stream().filter((e) -> e.getValue().getTopic().equals(topic)).findAny(); |
||||
|
if (subscribtionEntry.isPresent()) { |
||||
|
subscribtionEntry.get().getValue().addHandler(handler, once); |
||||
|
return subscribtionEntry.get().getValue().getFuture(); |
||||
|
} |
||||
|
} |
||||
|
if (this.serverSubscribtions.contains(topic)) { |
||||
|
MqttSubscribtion subscribtion = new MqttSubscribtion(topic, handler, once); |
||||
|
this.subscriptions.put(topic, subscribtion); |
||||
|
this.handlerToSubscribtion.put(handler, subscribtion); |
||||
|
return this.channel.newSucceededFuture(); |
||||
|
} |
||||
|
|
||||
|
Promise<Void> future = new DefaultPromise<>(this.eventLoop.next()); |
||||
|
MqttFixedHeader fixedHeader = new MqttFixedHeader(MqttMessageType.SUBSCRIBE, false, MqttQoS.AT_LEAST_ONCE, false, 0); |
||||
|
MqttTopicSubscription subscription = new MqttTopicSubscription(topic, qos); |
||||
|
MqttMessageIdVariableHeader variableHeader = getNewMessageId(); |
||||
|
MqttSubscribePayload payload = new MqttSubscribePayload(Collections.singletonList(subscription)); |
||||
|
MqttSubscribeMessage message = new MqttSubscribeMessage(fixedHeader, variableHeader, payload); |
||||
|
|
||||
|
final MqttPendingSubscribtion pendingSubscribtion = new MqttPendingSubscribtion(future, topic, message); |
||||
|
pendingSubscribtion.addHandler(handler, once); |
||||
|
this.pendingSubscribtions.put(variableHeader.messageId(), pendingSubscribtion); |
||||
|
this.pendingSubscribeTopics.add(topic); |
||||
|
pendingSubscribtion.setSent(this.sendAndFlushPacket(message) != null); //If not sent, we will send it when the connection is opened
|
||||
|
|
||||
|
pendingSubscribtion.startRetransmitTimer(this.eventLoop.next(), this::sendAndFlushPacket); |
||||
|
|
||||
|
return future; |
||||
|
} |
||||
|
|
||||
|
private void checkSubscribtions(String topic, Promise<Void> promise) { |
||||
|
if (!(this.subscriptions.containsKey(topic) && this.subscriptions.get(topic).size() != 0) && this.serverSubscribtions.contains(topic)) { |
||||
|
MqttFixedHeader fixedHeader = new MqttFixedHeader(MqttMessageType.UNSUBSCRIBE, false, MqttQoS.AT_LEAST_ONCE, false, 0); |
||||
|
MqttMessageIdVariableHeader variableHeader = getNewMessageId(); |
||||
|
MqttUnsubscribePayload payload = new MqttUnsubscribePayload(Collections.singletonList(topic)); |
||||
|
MqttUnsubscribeMessage message = new MqttUnsubscribeMessage(fixedHeader, variableHeader, payload); |
||||
|
|
||||
|
MqttPendingUnsubscribtion pendingUnsubscribtion = new MqttPendingUnsubscribtion(promise, topic, message); |
||||
|
this.pendingServerUnsubscribes.put(variableHeader.messageId(), pendingUnsubscribtion); |
||||
|
pendingUnsubscribtion.startRetransmissionTimer(this.eventLoop.next(), this::sendAndFlushPacket); |
||||
|
|
||||
|
this.sendAndFlushPacket(message); |
||||
|
} else { |
||||
|
promise.setSuccess(null); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
IntObjectHashMap<MqttPendingSubscribtion> getPendingSubscribtions() { |
||||
|
return pendingSubscribtions; |
||||
|
} |
||||
|
|
||||
|
HashMultimap<String, MqttSubscribtion> getSubscriptions() { |
||||
|
return subscriptions; |
||||
|
} |
||||
|
|
||||
|
Set<String> getPendingSubscribeTopics() { |
||||
|
return pendingSubscribeTopics; |
||||
|
} |
||||
|
|
||||
|
HashMultimap<MqttHandler, MqttSubscribtion> getHandlerToSubscribtion() { |
||||
|
return handlerToSubscribtion; |
||||
|
} |
||||
|
|
||||
|
Set<String> getServerSubscribtions() { |
||||
|
return serverSubscribtions; |
||||
|
} |
||||
|
|
||||
|
IntObjectHashMap<MqttPendingUnsubscribtion> getPendingServerUnsubscribes() { |
||||
|
return pendingServerUnsubscribes; |
||||
|
} |
||||
|
|
||||
|
IntObjectHashMap<MqttPendingPublish> getPendingPublishes() { |
||||
|
return pendingPublishes; |
||||
|
} |
||||
|
|
||||
|
IntObjectHashMap<MqttIncomingQos2Publish> getQos2PendingIncomingPublishes() { |
||||
|
return qos2PendingIncomingPublishes; |
||||
|
} |
||||
|
|
||||
|
private class MqttChannelInitializer extends ChannelInitializer<SocketChannel> { |
||||
|
|
||||
|
private final Promise<MqttConnectResult> connectFuture; |
||||
|
private final String host; |
||||
|
private final int port; |
||||
|
private final SslContext sslContext; |
||||
|
|
||||
|
|
||||
|
public MqttChannelInitializer(Promise<MqttConnectResult> connectFuture, String host, int port, SslContext sslContext) { |
||||
|
this.connectFuture = connectFuture; |
||||
|
this.host = host; |
||||
|
this.port = port; |
||||
|
this.sslContext = sslContext; |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
protected void initChannel(SocketChannel ch) throws Exception { |
||||
|
if (sslContext != null) { |
||||
|
ch.pipeline().addLast(sslContext.newHandler(ch.alloc(), host, port)); |
||||
|
} |
||||
|
|
||||
|
ch.pipeline().addLast("mqttDecoder", new MqttDecoder()); |
||||
|
ch.pipeline().addLast("mqttEncoder", MqttEncoder.INSTANCE); |
||||
|
ch.pipeline().addLast("idleStateHandler", new IdleStateHandler(MqttClientImpl.this.clientConfig.getTimeoutSeconds(), MqttClientImpl.this.clientConfig.getTimeoutSeconds(), 0)); |
||||
|
ch.pipeline().addLast("mqttPingHandler", new MqttPingHandler(MqttClientImpl.this.clientConfig.getTimeoutSeconds())); |
||||
|
ch.pipeline().addLast("mqttHandler", new MqttChannelHandler(MqttClientImpl.this, connectFuture)); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,45 @@ |
|||||
|
/** |
||||
|
* Copyright © 2016-2018 The Thingsboard Authors |
||||
|
* |
||||
|
* 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. |
||||
|
*/ |
||||
|
package org.thingsboard.mqtt; |
||||
|
|
||||
|
import io.netty.channel.ChannelFuture; |
||||
|
import io.netty.handler.codec.mqtt.MqttConnectReturnCode; |
||||
|
|
||||
|
@SuppressWarnings({"WeakerAccess", "unused"}) |
||||
|
public final class MqttConnectResult { |
||||
|
|
||||
|
private final boolean success; |
||||
|
private final MqttConnectReturnCode returnCode; |
||||
|
private final ChannelFuture closeFuture; |
||||
|
|
||||
|
MqttConnectResult(boolean success, MqttConnectReturnCode returnCode, ChannelFuture closeFuture) { |
||||
|
this.success = success; |
||||
|
this.returnCode = returnCode; |
||||
|
this.closeFuture = closeFuture; |
||||
|
} |
||||
|
|
||||
|
public boolean isSuccess() { |
||||
|
return success; |
||||
|
} |
||||
|
|
||||
|
public MqttConnectReturnCode getReturnCode() { |
||||
|
return returnCode; |
||||
|
} |
||||
|
|
||||
|
public ChannelFuture getCloseFuture() { |
||||
|
return closeFuture; |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,23 @@ |
|||||
|
/** |
||||
|
* Copyright © 2016-2018 The Thingsboard Authors |
||||
|
* |
||||
|
* 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. |
||||
|
*/ |
||||
|
package org.thingsboard.mqtt; |
||||
|
|
||||
|
import io.netty.buffer.ByteBuf; |
||||
|
|
||||
|
public interface MqttHandler { |
||||
|
|
||||
|
void onMessage(String topic, ByteBuf payload); |
||||
|
} |
||||
@ -0,0 +1,48 @@ |
|||||
|
/** |
||||
|
* Copyright © 2016-2018 The Thingsboard Authors |
||||
|
* |
||||
|
* 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. |
||||
|
*/ |
||||
|
package org.thingsboard.mqtt; |
||||
|
|
||||
|
import io.netty.channel.EventLoop; |
||||
|
import io.netty.handler.codec.mqtt.*; |
||||
|
|
||||
|
import java.util.function.Consumer; |
||||
|
|
||||
|
final class MqttIncomingQos2Publish { |
||||
|
|
||||
|
private final MqttPublishMessage incomingPublish; |
||||
|
|
||||
|
private final RetransmissionHandler<MqttMessage> retransmissionHandler = new RetransmissionHandler<>(); |
||||
|
|
||||
|
MqttIncomingQos2Publish(MqttPublishMessage incomingPublish, MqttMessage originalMessage) { |
||||
|
this.incomingPublish = incomingPublish; |
||||
|
|
||||
|
this.retransmissionHandler.setOriginalMessage(originalMessage); |
||||
|
} |
||||
|
|
||||
|
MqttPublishMessage getIncomingPublish() { |
||||
|
return incomingPublish; |
||||
|
} |
||||
|
|
||||
|
void startPubrecRetransmitTimer(EventLoop eventLoop, Consumer<Object> sendPacket) { |
||||
|
this.retransmissionHandler.setHandle((fixedHeader, originalMessage) -> |
||||
|
sendPacket.accept(new MqttMessage(fixedHeader, originalMessage.variableHeader()))); |
||||
|
this.retransmissionHandler.start(eventLoop); |
||||
|
} |
||||
|
|
||||
|
void onPubrelReceived() { |
||||
|
this.retransmissionHandler.stop(); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,154 @@ |
|||||
|
/** |
||||
|
* Copyright © 2016-2018 The Thingsboard Authors |
||||
|
* |
||||
|
* 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. |
||||
|
*/ |
||||
|
package org.thingsboard.mqtt; |
||||
|
|
||||
|
import io.netty.handler.codec.mqtt.MqttQoS; |
||||
|
|
||||
|
@SuppressWarnings({"WeakerAccess", "unused", "SimplifiableIfStatement", "StringBufferReplaceableByString"}) |
||||
|
public final class MqttLastWill { |
||||
|
|
||||
|
private final String topic; |
||||
|
private final String message; |
||||
|
private final boolean retain; |
||||
|
private final MqttQoS qos; |
||||
|
|
||||
|
public MqttLastWill(String topic, String message, boolean retain, MqttQoS qos) { |
||||
|
if(topic == null){ |
||||
|
throw new NullPointerException("topic"); |
||||
|
} |
||||
|
if(message == null){ |
||||
|
throw new NullPointerException("message"); |
||||
|
} |
||||
|
if(qos == null){ |
||||
|
throw new NullPointerException("qos"); |
||||
|
} |
||||
|
this.topic = topic; |
||||
|
this.message = message; |
||||
|
this.retain = retain; |
||||
|
this.qos = qos; |
||||
|
} |
||||
|
|
||||
|
public String getTopic() { |
||||
|
return topic; |
||||
|
} |
||||
|
|
||||
|
public String getMessage() { |
||||
|
return message; |
||||
|
} |
||||
|
|
||||
|
public boolean isRetain() { |
||||
|
return retain; |
||||
|
} |
||||
|
|
||||
|
public MqttQoS getQos() { |
||||
|
return qos; |
||||
|
} |
||||
|
|
||||
|
public static MqttLastWill.Builder builder(){ |
||||
|
return new MqttLastWill.Builder(); |
||||
|
} |
||||
|
|
||||
|
public static final class Builder { |
||||
|
|
||||
|
private String topic; |
||||
|
private String message; |
||||
|
private boolean retain; |
||||
|
private MqttQoS qos; |
||||
|
|
||||
|
public String getTopic() { |
||||
|
return topic; |
||||
|
} |
||||
|
|
||||
|
public Builder setTopic(String topic) { |
||||
|
if(topic == null){ |
||||
|
throw new NullPointerException("topic"); |
||||
|
} |
||||
|
this.topic = topic; |
||||
|
return this; |
||||
|
} |
||||
|
|
||||
|
public String getMessage() { |
||||
|
return message; |
||||
|
} |
||||
|
|
||||
|
public Builder setMessage(String message) { |
||||
|
if(message == null){ |
||||
|
throw new NullPointerException("message"); |
||||
|
} |
||||
|
this.message = message; |
||||
|
return this; |
||||
|
} |
||||
|
|
||||
|
public boolean isRetain() { |
||||
|
return retain; |
||||
|
} |
||||
|
|
||||
|
public Builder setRetain(boolean retain) { |
||||
|
this.retain = retain; |
||||
|
return this; |
||||
|
} |
||||
|
|
||||
|
public MqttQoS getQos() { |
||||
|
return qos; |
||||
|
} |
||||
|
|
||||
|
public Builder setQos(MqttQoS qos) { |
||||
|
if(qos == null){ |
||||
|
throw new NullPointerException("qos"); |
||||
|
} |
||||
|
this.qos = qos; |
||||
|
return this; |
||||
|
} |
||||
|
|
||||
|
public MqttLastWill build(){ |
||||
|
return new MqttLastWill(topic, message, retain, qos); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public boolean equals(Object o) { |
||||
|
if (this == o) return true; |
||||
|
if (o == null || getClass() != o.getClass()) return false; |
||||
|
|
||||
|
MqttLastWill that = (MqttLastWill) o; |
||||
|
|
||||
|
if (retain != that.retain) return false; |
||||
|
if (!topic.equals(that.topic)) return false; |
||||
|
if (!message.equals(that.message)) return false; |
||||
|
return qos == that.qos; |
||||
|
|
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public int hashCode() { |
||||
|
int result = topic.hashCode(); |
||||
|
result = 31 * result + message.hashCode(); |
||||
|
result = 31 * result + (retain ? 1 : 0); |
||||
|
result = 31 * result + qos.hashCode(); |
||||
|
return result; |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public String toString() { |
||||
|
final StringBuilder sb = new StringBuilder("MqttLastWill{"); |
||||
|
sb.append("topic='").append(topic).append('\''); |
||||
|
sb.append(", message='").append(message).append('\''); |
||||
|
sb.append(", retain=").append(retain); |
||||
|
sb.append(", qos=").append(qos.name()); |
||||
|
sb.append('}'); |
||||
|
return sb.toString(); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,101 @@ |
|||||
|
/** |
||||
|
* Copyright © 2016-2018 The Thingsboard Authors |
||||
|
* |
||||
|
* 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. |
||||
|
*/ |
||||
|
package org.thingsboard.mqtt; |
||||
|
|
||||
|
import io.netty.buffer.ByteBuf; |
||||
|
import io.netty.channel.EventLoop; |
||||
|
import io.netty.handler.codec.mqtt.MqttMessage; |
||||
|
import io.netty.handler.codec.mqtt.MqttPublishMessage; |
||||
|
import io.netty.handler.codec.mqtt.MqttQoS; |
||||
|
import io.netty.util.concurrent.Promise; |
||||
|
|
||||
|
import java.util.function.Consumer; |
||||
|
|
||||
|
final class MqttPendingPublish { |
||||
|
|
||||
|
private final int messageId; |
||||
|
private final Promise<Void> future; |
||||
|
private final ByteBuf payload; |
||||
|
private final MqttPublishMessage message; |
||||
|
private final MqttQoS qos; |
||||
|
|
||||
|
private final RetransmissionHandler<MqttPublishMessage> publishRetransmissionHandler = new RetransmissionHandler<>(); |
||||
|
private final RetransmissionHandler<MqttMessage> pubrelRetransmissionHandler = new RetransmissionHandler<>(); |
||||
|
|
||||
|
private boolean sent = false; |
||||
|
|
||||
|
MqttPendingPublish(int messageId, Promise<Void> future, ByteBuf payload, MqttPublishMessage message, MqttQoS qos) { |
||||
|
this.messageId = messageId; |
||||
|
this.future = future; |
||||
|
this.payload = payload; |
||||
|
this.message = message; |
||||
|
this.qos = qos; |
||||
|
|
||||
|
this.publishRetransmissionHandler.setOriginalMessage(message); |
||||
|
} |
||||
|
|
||||
|
int getMessageId() { |
||||
|
return messageId; |
||||
|
} |
||||
|
|
||||
|
Promise<Void> getFuture() { |
||||
|
return future; |
||||
|
} |
||||
|
|
||||
|
ByteBuf getPayload() { |
||||
|
return payload; |
||||
|
} |
||||
|
|
||||
|
boolean isSent() { |
||||
|
return sent; |
||||
|
} |
||||
|
|
||||
|
void setSent(boolean sent) { |
||||
|
this.sent = sent; |
||||
|
} |
||||
|
|
||||
|
MqttPublishMessage getMessage() { |
||||
|
return message; |
||||
|
} |
||||
|
|
||||
|
MqttQoS getQos() { |
||||
|
return qos; |
||||
|
} |
||||
|
|
||||
|
void startPublishRetransmissionTimer(EventLoop eventLoop, Consumer<Object> sendPacket) { |
||||
|
this.publishRetransmissionHandler.setHandle(((fixedHeader, originalMessage) -> |
||||
|
sendPacket.accept(new MqttPublishMessage(fixedHeader, originalMessage.variableHeader(), this.payload.retain())))); |
||||
|
this.publishRetransmissionHandler.start(eventLoop); |
||||
|
} |
||||
|
|
||||
|
void onPubackReceived() { |
||||
|
this.publishRetransmissionHandler.stop(); |
||||
|
} |
||||
|
|
||||
|
void setPubrelMessage(MqttMessage pubrelMessage) { |
||||
|
this.pubrelRetransmissionHandler.setOriginalMessage(pubrelMessage); |
||||
|
} |
||||
|
|
||||
|
void startPubrelRetransmissionTimer(EventLoop eventLoop, Consumer<Object> sendPacket) { |
||||
|
this.pubrelRetransmissionHandler.setHandle((fixedHeader, originalMessage) -> |
||||
|
sendPacket.accept(new MqttMessage(fixedHeader, originalMessage.variableHeader()))); |
||||
|
this.pubrelRetransmissionHandler.start(eventLoop); |
||||
|
} |
||||
|
|
||||
|
void onPubcompReceived() { |
||||
|
this.pubrelRetransmissionHandler.stop(); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,102 @@ |
|||||
|
/** |
||||
|
* Copyright © 2016-2018 The Thingsboard Authors |
||||
|
* |
||||
|
* 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. |
||||
|
*/ |
||||
|
package org.thingsboard.mqtt; |
||||
|
|
||||
|
import io.netty.channel.EventLoop; |
||||
|
import io.netty.handler.codec.mqtt.MqttSubscribeMessage; |
||||
|
import io.netty.util.concurrent.Promise; |
||||
|
|
||||
|
import java.util.HashSet; |
||||
|
import java.util.Set; |
||||
|
import java.util.function.Consumer; |
||||
|
|
||||
|
final class MqttPendingSubscribtion { |
||||
|
|
||||
|
private final Promise<Void> future; |
||||
|
private final String topic; |
||||
|
private final Set<MqttPendingHandler> handlers = new HashSet<>(); |
||||
|
private final MqttSubscribeMessage subscribeMessage; |
||||
|
|
||||
|
private final RetransmissionHandler<MqttSubscribeMessage> retransmissionHandler = new RetransmissionHandler<>(); |
||||
|
|
||||
|
private boolean sent = false; |
||||
|
|
||||
|
MqttPendingSubscribtion(Promise<Void> future, String topic, MqttSubscribeMessage message) { |
||||
|
this.future = future; |
||||
|
this.topic = topic; |
||||
|
this.subscribeMessage = message; |
||||
|
|
||||
|
this.retransmissionHandler.setOriginalMessage(message); |
||||
|
} |
||||
|
|
||||
|
Promise<Void> getFuture() { |
||||
|
return future; |
||||
|
} |
||||
|
|
||||
|
String getTopic() { |
||||
|
return topic; |
||||
|
} |
||||
|
|
||||
|
boolean isSent() { |
||||
|
return sent; |
||||
|
} |
||||
|
|
||||
|
void setSent(boolean sent) { |
||||
|
this.sent = sent; |
||||
|
} |
||||
|
|
||||
|
MqttSubscribeMessage getSubscribeMessage() { |
||||
|
return subscribeMessage; |
||||
|
} |
||||
|
|
||||
|
void addHandler(MqttHandler handler, boolean once){ |
||||
|
this.handlers.add(new MqttPendingHandler(handler, once)); |
||||
|
} |
||||
|
|
||||
|
Set<MqttPendingHandler> getHandlers() { |
||||
|
return handlers; |
||||
|
} |
||||
|
|
||||
|
void startRetransmitTimer(EventLoop eventLoop, Consumer<Object> sendPacket) { |
||||
|
if(this.sent){ //If the packet is sent, we can start the retransmit timer
|
||||
|
this.retransmissionHandler.setHandle((fixedHeader, originalMessage) -> |
||||
|
sendPacket.accept(new MqttSubscribeMessage(fixedHeader, originalMessage.variableHeader(), originalMessage.payload()))); |
||||
|
this.retransmissionHandler.start(eventLoop); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
void onSubackReceived(){ |
||||
|
this.retransmissionHandler.stop(); |
||||
|
} |
||||
|
|
||||
|
final class MqttPendingHandler { |
||||
|
private final MqttHandler handler; |
||||
|
private final boolean once; |
||||
|
|
||||
|
MqttPendingHandler(MqttHandler handler, boolean once) { |
||||
|
this.handler = handler; |
||||
|
this.once = once; |
||||
|
} |
||||
|
|
||||
|
MqttHandler getHandler() { |
||||
|
return handler; |
||||
|
} |
||||
|
|
||||
|
boolean isOnce() { |
||||
|
return once; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,55 @@ |
|||||
|
/** |
||||
|
* Copyright © 2016-2018 The Thingsboard Authors |
||||
|
* |
||||
|
* 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. |
||||
|
*/ |
||||
|
package org.thingsboard.mqtt; |
||||
|
|
||||
|
import io.netty.channel.EventLoop; |
||||
|
import io.netty.handler.codec.mqtt.MqttUnsubscribeMessage; |
||||
|
import io.netty.util.concurrent.Promise; |
||||
|
|
||||
|
import java.util.function.Consumer; |
||||
|
|
||||
|
final class MqttPendingUnsubscribtion { |
||||
|
|
||||
|
private final Promise<Void> future; |
||||
|
private final String topic; |
||||
|
|
||||
|
private final RetransmissionHandler<MqttUnsubscribeMessage> retransmissionHandler = new RetransmissionHandler<>(); |
||||
|
|
||||
|
MqttPendingUnsubscribtion(Promise<Void> future, String topic, MqttUnsubscribeMessage unsubscribeMessage) { |
||||
|
this.future = future; |
||||
|
this.topic = topic; |
||||
|
|
||||
|
this.retransmissionHandler.setOriginalMessage(unsubscribeMessage); |
||||
|
} |
||||
|
|
||||
|
Promise<Void> getFuture() { |
||||
|
return future; |
||||
|
} |
||||
|
|
||||
|
String getTopic() { |
||||
|
return topic; |
||||
|
} |
||||
|
|
||||
|
void startRetransmissionTimer(EventLoop eventLoop, Consumer<Object> sendPacket) { |
||||
|
this.retransmissionHandler.setHandle((fixedHeader, originalMessage) -> |
||||
|
sendPacket.accept(new MqttUnsubscribeMessage(fixedHeader, originalMessage.variableHeader(), originalMessage.payload()))); |
||||
|
this.retransmissionHandler.start(eventLoop); |
||||
|
} |
||||
|
|
||||
|
void onUnsubackReceived(){ |
||||
|
this.retransmissionHandler.stop(); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,98 @@ |
|||||
|
/** |
||||
|
* Copyright © 2016-2018 The Thingsboard Authors |
||||
|
* |
||||
|
* 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. |
||||
|
*/ |
||||
|
package org.thingsboard.mqtt; |
||||
|
|
||||
|
import io.netty.channel.Channel; |
||||
|
import io.netty.channel.ChannelFutureListener; |
||||
|
import io.netty.channel.ChannelHandlerContext; |
||||
|
import io.netty.channel.ChannelInboundHandlerAdapter; |
||||
|
import io.netty.handler.codec.mqtt.MqttFixedHeader; |
||||
|
import io.netty.handler.codec.mqtt.MqttMessage; |
||||
|
import io.netty.handler.codec.mqtt.MqttMessageType; |
||||
|
import io.netty.handler.codec.mqtt.MqttQoS; |
||||
|
import io.netty.handler.timeout.IdleStateEvent; |
||||
|
import io.netty.util.ReferenceCountUtil; |
||||
|
import io.netty.util.concurrent.ScheduledFuture; |
||||
|
|
||||
|
import java.util.concurrent.TimeUnit; |
||||
|
|
||||
|
final class MqttPingHandler extends ChannelInboundHandlerAdapter { |
||||
|
|
||||
|
private final int keepaliveSeconds; |
||||
|
|
||||
|
private ScheduledFuture<?> pingRespTimeout; |
||||
|
|
||||
|
MqttPingHandler(int keepaliveSeconds) { |
||||
|
this.keepaliveSeconds = keepaliveSeconds; |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { |
||||
|
if (!(msg instanceof MqttMessage)) { |
||||
|
ctx.fireChannelRead(msg); |
||||
|
return; |
||||
|
} |
||||
|
MqttMessage message = (MqttMessage) msg; |
||||
|
if(message.fixedHeader().messageType() == MqttMessageType.PINGREQ){ |
||||
|
this.handlePingReq(ctx.channel()); |
||||
|
} else if(message.fixedHeader().messageType() == MqttMessageType.PINGRESP){ |
||||
|
this.handlePingResp(); |
||||
|
}else{ |
||||
|
ctx.fireChannelRead(ReferenceCountUtil.retain(msg)); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception { |
||||
|
super.userEventTriggered(ctx, evt); |
||||
|
|
||||
|
if(evt instanceof IdleStateEvent){ |
||||
|
IdleStateEvent event = (IdleStateEvent) evt; |
||||
|
switch(event.state()){ |
||||
|
case READER_IDLE: |
||||
|
break; |
||||
|
case WRITER_IDLE: |
||||
|
this.sendPingReq(ctx.channel()); |
||||
|
break; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private void sendPingReq(Channel channel){ |
||||
|
MqttFixedHeader fixedHeader = new MqttFixedHeader(MqttMessageType.PINGREQ, false, MqttQoS.AT_MOST_ONCE, false, 0); |
||||
|
channel.writeAndFlush(new MqttMessage(fixedHeader)); |
||||
|
|
||||
|
if(this.pingRespTimeout != null){ |
||||
|
this.pingRespTimeout = channel.eventLoop().schedule(() -> { |
||||
|
MqttFixedHeader fixedHeader2 = new MqttFixedHeader(MqttMessageType.DISCONNECT, false, MqttQoS.AT_MOST_ONCE, false, 0); |
||||
|
channel.writeAndFlush(new MqttMessage(fixedHeader2)).addListener(ChannelFutureListener.CLOSE); |
||||
|
//TODO: what do when the connection is closed ?
|
||||
|
}, this.keepaliveSeconds, TimeUnit.SECONDS); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private void handlePingReq(Channel channel){ |
||||
|
MqttFixedHeader fixedHeader = new MqttFixedHeader(MqttMessageType.PINGRESP, false, MqttQoS.AT_MOST_ONCE, false, 0); |
||||
|
channel.writeAndFlush(new MqttMessage(fixedHeader)); |
||||
|
} |
||||
|
|
||||
|
private void handlePingResp(){ |
||||
|
if(this.pingRespTimeout != null && !this.pingRespTimeout.isCancelled() && !this.pingRespTimeout.isDone()){ |
||||
|
this.pingRespTimeout.cancel(true); |
||||
|
this.pingRespTimeout = null; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,84 @@ |
|||||
|
/** |
||||
|
* Copyright © 2016-2018 The Thingsboard Authors |
||||
|
* |
||||
|
* 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. |
||||
|
*/ |
||||
|
package org.thingsboard.mqtt; |
||||
|
|
||||
|
import java.util.regex.Pattern; |
||||
|
|
||||
|
final class MqttSubscribtion { |
||||
|
|
||||
|
private final String topic; |
||||
|
private final Pattern topicRegex; |
||||
|
private final MqttHandler handler; |
||||
|
|
||||
|
private final boolean once; |
||||
|
|
||||
|
private boolean called; |
||||
|
|
||||
|
MqttSubscribtion(String topic, MqttHandler handler, boolean once) { |
||||
|
if(topic == null){ |
||||
|
throw new NullPointerException("topic"); |
||||
|
} |
||||
|
if(handler == null){ |
||||
|
throw new NullPointerException("handler"); |
||||
|
} |
||||
|
this.topic = topic; |
||||
|
this.handler = handler; |
||||
|
this.once = once; |
||||
|
this.topicRegex = Pattern.compile(topic.replace("+", "[^/]+").replace("#", ".+") + "$"); |
||||
|
} |
||||
|
|
||||
|
String getTopic() { |
||||
|
return topic; |
||||
|
} |
||||
|
|
||||
|
public MqttHandler getHandler() { |
||||
|
return handler; |
||||
|
} |
||||
|
|
||||
|
boolean isOnce() { |
||||
|
return once; |
||||
|
} |
||||
|
|
||||
|
boolean isCalled() { |
||||
|
return called; |
||||
|
} |
||||
|
|
||||
|
boolean matches(String topic){ |
||||
|
return this.topicRegex.matcher(topic).matches(); |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public boolean equals(Object o) { |
||||
|
if (this == o) return true; |
||||
|
if (o == null || getClass() != o.getClass()) return false; |
||||
|
|
||||
|
MqttSubscribtion that = (MqttSubscribtion) o; |
||||
|
|
||||
|
return once == that.once && topic.equals(that.topic) && handler.equals(that.handler); |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public int hashCode() { |
||||
|
int result = topic.hashCode(); |
||||
|
result = 31 * result + handler.hashCode(); |
||||
|
result = 31 * result + (once ? 1 : 0); |
||||
|
return result; |
||||
|
} |
||||
|
|
||||
|
void setCalled(boolean called) { |
||||
|
this.called = called; |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,66 @@ |
|||||
|
/** |
||||
|
* Copyright © 2016-2018 The Thingsboard Authors |
||||
|
* |
||||
|
* 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. |
||||
|
*/ |
||||
|
package org.thingsboard.mqtt; |
||||
|
|
||||
|
import io.netty.channel.EventLoop; |
||||
|
import io.netty.handler.codec.mqtt.MqttFixedHeader; |
||||
|
import io.netty.handler.codec.mqtt.MqttMessage; |
||||
|
import io.netty.util.concurrent.ScheduledFuture; |
||||
|
|
||||
|
import java.util.concurrent.TimeUnit; |
||||
|
import java.util.function.BiConsumer; |
||||
|
|
||||
|
final class RetransmissionHandler<T extends MqttMessage> { |
||||
|
|
||||
|
private ScheduledFuture<?> timer; |
||||
|
private int timeout = 10; |
||||
|
private BiConsumer<MqttFixedHeader, T> handler; |
||||
|
private T originalMessage; |
||||
|
|
||||
|
void start(EventLoop eventLoop){ |
||||
|
if(eventLoop == null){ |
||||
|
throw new NullPointerException("eventLoop"); |
||||
|
} |
||||
|
if(this.handler == null){ |
||||
|
throw new NullPointerException("handler"); |
||||
|
} |
||||
|
this.timeout = 10; |
||||
|
this.startTimer(eventLoop); |
||||
|
} |
||||
|
|
||||
|
private void startTimer(EventLoop eventLoop){ |
||||
|
this.timer = eventLoop.schedule(() -> { |
||||
|
this.timeout += 5; |
||||
|
MqttFixedHeader fixedHeader = new MqttFixedHeader(this.originalMessage.fixedHeader().messageType(), true, this.originalMessage.fixedHeader().qosLevel(), this.originalMessage.fixedHeader().isRetain(), this.originalMessage.fixedHeader().remainingLength()); |
||||
|
handler.accept(fixedHeader, originalMessage); |
||||
|
startTimer(eventLoop); |
||||
|
}, timeout, TimeUnit.SECONDS); |
||||
|
} |
||||
|
|
||||
|
void stop(){ |
||||
|
if(this.timer != null){ |
||||
|
this.timer.cancel(true); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
void setHandle(BiConsumer<MqttFixedHeader, T> runnable) { |
||||
|
this.handler = runnable; |
||||
|
} |
||||
|
|
||||
|
void setOriginalMessage(T originalMessage) { |
||||
|
this.originalMessage = originalMessage; |
||||
|
} |
||||
|
} |
||||
Loading…
Reference in new issue