GH-3732: Fix NPE in Mqttv5PahoMessageDrivenChA

Fixes https://github.com/spring-projects/spring-integration/issues/3732

The `Mqttv5PahoMessageDrivenChannelAdapter` unconditionally tries to (un)subscribe
to/from topics when the `mqqtClient` might not be initialized yet.

* Add `mqqtClient` initialization check before adding or removing topic.
Re-align logic with the `MqttPahoMessageDrivenChannelAdapter`

**Cherry-pick to `5.5.x`**
This commit is contained in:
Mikhail Polivakha
2022-03-14 16:47:57 -04:00
committed by Artem Bilan
parent 575584d2aa
commit 4bf606a085
3 changed files with 43 additions and 24 deletions

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2021 the original author or authors.
* Copyright 2002-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -17,6 +17,7 @@
package org.springframework.integration.mqtt.inbound;
import java.util.LinkedHashSet;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
@@ -40,6 +41,7 @@ import org.springframework.util.Assert;
* @author Gary Russell
* @author Artem Bilan
* @author Trung Pham
* @author Mikhail Polivakha
*
* @since 4.0
*
@@ -331,15 +333,7 @@ public abstract class AbstractMqttMessageDrivenChannelAdapter extends MessagePro
return false;
}
Topic other = (Topic) obj;
if (this.topic == null) {
if (other.topic != null) {
return false;
}
}
else if (!this.topic.equals(other.topic)) {
return false;
}
return true;
return Objects.equals(this.topic, other.topic);
}
@Override

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2021 the original author or authors.
* Copyright 2021-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -65,6 +65,7 @@ import org.springframework.util.Assert;
* See {@link #setPayloadType} for more information about type conversion.
*
* @author Artem Bilan
* @author Mikhail Polivakha
*
* @since 5.5.5
*
@@ -141,13 +142,15 @@ public class Mqttv5PahoMessageDrivenChannelAdapter extends AbstractMqttMessageDr
@Override
protected void onInit() {
super.onInit();
try {
this.mqttClient = new MqttAsyncClient(getUrl(), getClientId(), this.persistence);
this.mqttClient.setCallback(this);
this.mqttClient.setManualAcks(isManualAcks());
}
catch (MqttException ex) {
throw new BeanCreationException("Cannot create 'MqttAsyncClient' for: " + getComponentName(), ex);
if (this.mqttClient == null) {
try {
this.mqttClient = new MqttAsyncClient(getUrl(), getClientId(), this.persistence);
this.mqttClient.setCallback(this);
this.mqttClient.setManualAcks(isManualAcks());
}
catch (MqttException ex) {
throw new BeanCreationException("Cannot create 'MqttAsyncClient' for: " + getComponentName(), ex);
}
}
if (this.messageConverter == null) {
setMessageConverter(getBeanFactory()
@@ -189,8 +192,10 @@ public class Mqttv5PahoMessageDrivenChannelAdapter extends AbstractMqttMessageDr
this.topicLock.lock();
String[] topics = getTopic();
try {
this.mqttClient.unsubscribe(topics).waitForCompletion(getCompletionTimeout());
this.mqttClient.disconnect().waitForCompletion(getCompletionTimeout());
if (this.mqttClient != null && this.mqttClient.isConnected()) {
this.mqttClient.unsubscribe(topics).waitForCompletion(getCompletionTimeout());
this.mqttClient.disconnect().waitForCompletion(getCompletionTimeout());
}
}
catch (MqttException ex) {
logger.error(ex, () -> "Error unsubscribing from " + Arrays.toString(topics));
@@ -204,7 +209,9 @@ public class Mqttv5PahoMessageDrivenChannelAdapter extends AbstractMqttMessageDr
public void destroy() {
super.destroy();
try {
this.mqttClient.close(true);
if (this.mqttClient != null) {
this.mqttClient.close(true);
}
}
catch (MqttException ex) {
logger.error(ex, "Failed to close 'MqttAsyncClient'");
@@ -215,8 +222,10 @@ public class Mqttv5PahoMessageDrivenChannelAdapter extends AbstractMqttMessageDr
public void addTopic(String topic, int qos) {
this.topicLock.lock();
try {
this.mqttClient.subscribe(topic, qos).waitForCompletion(getCompletionTimeout());
super.addTopic(topic, qos);
if (this.mqttClient != null && this.mqttClient.isConnected()) {
this.mqttClient.subscribe(topic, qos).waitForCompletion(getCompletionTimeout());
}
}
catch (MqttException ex) {
throw new MessagingException("Failed to subscribe to topic " + topic, ex);
@@ -230,7 +239,9 @@ public class Mqttv5PahoMessageDrivenChannelAdapter extends AbstractMqttMessageDr
public void removeTopic(String... topic) {
this.topicLock.lock();
try {
this.mqttClient.unsubscribe(topic).waitForCompletion(getCompletionTimeout());
if (this.mqttClient != null && this.mqttClient.isConnected()) {
this.mqttClient.unsubscribe(topic).waitForCompletion(getCompletionTimeout());
}
super.removeTopic(topic);
}
catch (MqttException ex) {

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2021 the original author or authors.
* Copyright 2002-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -22,6 +22,7 @@ import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
@@ -55,6 +56,7 @@ import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;
/**
* @author Gary Russell
* @author Artem Bilan
* @author Mikhail Polivakha
*
* @since 5.5.5
*
@@ -73,6 +75,18 @@ public class Mqttv5BackToBackTests implements MosquittoContainerTest {
@Autowired
private Config config;
@Test //GH-3732
public void testNoNpeIsNotThrownInCaseDoInitIsNotInvokedBeforeTopicAddition() {
Mqttv5PahoMessageDrivenChannelAdapter channelAdapter = new Mqttv5PahoMessageDrivenChannelAdapter("tcp://mock-url.com:8091", "mock-client-id", "123");
Assertions.assertDoesNotThrow(() -> channelAdapter.addTopic("abc", 1));
}
@Test //GH-3732
public void testNoNpeIsNotThrownInCaseDoInitIsNotInvokedBeforeTopicRemoval() {
Mqttv5PahoMessageDrivenChannelAdapter channelAdapter = new Mqttv5PahoMessageDrivenChannelAdapter("tcp://mock-url.com:8091", "mock-client-id", "123");
Assertions.assertDoesNotThrow(() -> channelAdapter.removeTopic("abc"));
}
@Test
public void testSimpleMqttv5Interaction() {
String testPayload = "foo";