
com.hivemq.mqtt.handler.unsubscribe.UnsubscribeHandler Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of hivemq-community-edition-embedded Show documentation
Show all versions of hivemq-community-edition-embedded Show documentation
HiveMQ CE is a Java-based open source MQTT broker that fully supports MQTT 3.x and MQTT 5
The newest version!
/*
* Copyright 2019-present HiveMQ GmbH
*
* 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 com.hivemq.mqtt.handler.unsubscribe;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.common.util.concurrent.FutureCallback;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.MoreExecutors;
import com.hivemq.bootstrap.ClientConnection;
import com.hivemq.extension.sdk.api.annotations.NotNull;
import com.hivemq.mqtt.handler.connect.SubscribeMessageBarrier;
import com.hivemq.mqtt.message.ProtocolVersion;
import com.hivemq.mqtt.message.reason.Mqtt5UnsubAckReasonCode;
import com.hivemq.mqtt.message.unsuback.UNSUBACK;
import com.hivemq.mqtt.message.unsubscribe.UNSUBSCRIBE;
import com.hivemq.persistence.clientsession.ClientSessionSubscriptionPersistence;
import com.hivemq.persistence.clientsession.SharedSubscriptionService;
import com.hivemq.util.Exceptions;
import io.netty.channel.ChannelHandler;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.inject.Inject;
import javax.inject.Singleton;
import java.util.Arrays;
import static com.google.common.base.Preconditions.checkNotNull;
@Singleton
@ChannelHandler.Sharable
public class UnsubscribeHandler extends SimpleChannelInboundHandler {
private static final Logger log = LoggerFactory.getLogger(UnsubscribeHandler.class);
private final @NotNull ClientSessionSubscriptionPersistence clientSessionSubscriptionPersistence;
private final @NotNull SharedSubscriptionService sharedSubscriptionService;
@Inject
public UnsubscribeHandler(
final @NotNull ClientSessionSubscriptionPersistence clientSessionSubscriptionPersistence,
final @NotNull SharedSubscriptionService sharedSubscriptionService) {
this.clientSessionSubscriptionPersistence = clientSessionSubscriptionPersistence;
this.sharedSubscriptionService = sharedSubscriptionService;
}
@Override
protected void channelRead0(
final @NotNull ChannelHandlerContext ctx, final @NotNull UNSUBSCRIBE msg) throws Exception {
SubscribeMessageBarrier.addToPipeline(ctx);
final ClientConnection clientConnection = ClientConnection.of(ctx.channel());
final String clientId = checkNotNull(clientConnection.getClientId());
final UnsubscribeOperationCompletionCallback unsubscribeOperationCompletionCallback =
new UnsubscribeOperationCompletionCallback(ctx,
sharedSubscriptionService,
clientConnection.getProtocolVersion(),
clientId,
msg.getTopics(),
msg.getPacketIdentifier());
if (msg.getTopics().size() == 1) { // Single unsubscribe.
final String topic = msg.getTopics().get(0);
final ListenableFuture future = clientSessionSubscriptionPersistence.remove(clientId, topic);
future.addListener(() -> {
if (log.isTraceEnabled()) {
log.trace("Unsubscribed from topic [{}] for client [{}]", topic, clientId);
}
}, MoreExecutors.directExecutor());
Futures.addCallback(future, unsubscribeOperationCompletionCallback, ctx.executor());
if (log.isTraceEnabled()) {
log.trace("Applied all unsubscriptions for client [{}]", clientId);
}
return;
}
// Batch unsubscribe. The decoded UNSUBSCRIBE message is guaranteed to have at least one topic filter.
final ListenableFuture future = clientSessionSubscriptionPersistence.removeSubscriptions(clientId,
ImmutableSet.copyOf(msg.getTopics()));
future.addListener(() -> msg.getTopics().forEach(topic -> {
if (log.isTraceEnabled()) {
log.trace("Unsubscribed from topic [{}] for client [{}]", topic, clientId);
}
}), MoreExecutors.directExecutor());
Futures.addCallback(future, unsubscribeOperationCompletionCallback, ctx.executor());
if (log.isTraceEnabled()) {
log.trace("Applied all unsubscriptions for client [{}]", clientId);
}
}
private static class UnsubscribeOperationCompletionCallback implements FutureCallback {
private final @NotNull ChannelHandlerContext ctx;
private final @NotNull SharedSubscriptionService sharedSubscriptionService;
private final @NotNull ProtocolVersion protocolVersion;
private final @NotNull String clientId;
private final @NotNull ImmutableList topicFilters;
private final int packetIdentifier;
UnsubscribeOperationCompletionCallback(
final @NotNull ChannelHandlerContext ctx,
final @NotNull SharedSubscriptionService sharedSubscriptionService,
final @NotNull ProtocolVersion protocolVersion,
final @NotNull String clientId,
final @NotNull ImmutableList topicFilters,
final int packetIdentifier) {
this.ctx = ctx;
this.sharedSubscriptionService = sharedSubscriptionService;
this.protocolVersion = protocolVersion;
this.clientId = clientId;
this.topicFilters = topicFilters;
this.packetIdentifier = packetIdentifier;
}
@Override
public void onSuccess(final @NotNull Void aVoid) {
// We only need to invalidate the caches for the node at which the client is connected,
// since this node decides which client will receive messages for the shared subscriptions
for (final String topicFilter : topicFilters) {
final SharedSubscriptionService.SharedSubscription sharedSubscription =
SharedSubscriptionService.checkForSharedSubscription(topicFilter);
if (sharedSubscription != null) {
sharedSubscriptionService.invalidateSharedSubscriberCache(sharedSubscription.getShareName() +
"/" +
sharedSubscription.getTopicFilter());
sharedSubscriptionService.invalidateSharedSubscriptionCache(clientId);
}
}
if (ProtocolVersion.MQTTv5 == protocolVersion) {
final Mqtt5UnsubAckReasonCode[] reasonCodes = new Mqtt5UnsubAckReasonCode[topicFilters.size()];
Arrays.fill(reasonCodes, Mqtt5UnsubAckReasonCode.SUCCESS);
ctx.writeAndFlush(new UNSUBACK(packetIdentifier, reasonCodes));
} else {
ctx.writeAndFlush(new UNSUBACK(packetIdentifier));
}
}
@Override
public void onFailure(final @NotNull Throwable throwable) {
//DON'T ACK for MQTT 3
if (ProtocolVersion.MQTTv5 == protocolVersion) {
final Mqtt5UnsubAckReasonCode[] reasonCodes = new Mqtt5UnsubAckReasonCode[topicFilters.size()];
Arrays.fill(reasonCodes, Mqtt5UnsubAckReasonCode.UNSPECIFIED_ERROR);
ctx.writeAndFlush(new UNSUBACK(packetIdentifier, reasonCodes));
}
Exceptions.rethrowError("Unable to unsubscribe client " + clientId + ".", throwable);
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy