From 4bf606a0859541ddbe99a62d16bb4973f29cd4a7 Mon Sep 17 00:00:00 2001 From: Mikhail Polivakha Date: Mon, 14 Mar 2022 16:47:57 -0400 Subject: [PATCH] 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`** --- ...stractMqttMessageDrivenChannelAdapter.java | 14 ++----- ...Mqttv5PahoMessageDrivenChannelAdapter.java | 37 ++++++++++++------- .../mqtt/Mqttv5BackToBackTests.java | 16 +++++++- 3 files changed, 43 insertions(+), 24 deletions(-) diff --git a/spring-integration-mqtt/src/main/java/org/springframework/integration/mqtt/inbound/AbstractMqttMessageDrivenChannelAdapter.java b/spring-integration-mqtt/src/main/java/org/springframework/integration/mqtt/inbound/AbstractMqttMessageDrivenChannelAdapter.java index e40c7e0578..da2d3f2c7a 100644 --- a/spring-integration-mqtt/src/main/java/org/springframework/integration/mqtt/inbound/AbstractMqttMessageDrivenChannelAdapter.java +++ b/spring-integration-mqtt/src/main/java/org/springframework/integration/mqtt/inbound/AbstractMqttMessageDrivenChannelAdapter.java @@ -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 diff --git a/spring-integration-mqtt/src/main/java/org/springframework/integration/mqtt/inbound/Mqttv5PahoMessageDrivenChannelAdapter.java b/spring-integration-mqtt/src/main/java/org/springframework/integration/mqtt/inbound/Mqttv5PahoMessageDrivenChannelAdapter.java index d9b3ce87ac..84fd94b709 100644 --- a/spring-integration-mqtt/src/main/java/org/springframework/integration/mqtt/inbound/Mqttv5PahoMessageDrivenChannelAdapter.java +++ b/spring-integration-mqtt/src/main/java/org/springframework/integration/mqtt/inbound/Mqttv5PahoMessageDrivenChannelAdapter.java @@ -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) { diff --git a/spring-integration-mqtt/src/test/java/org/springframework/integration/mqtt/Mqttv5BackToBackTests.java b/spring-integration-mqtt/src/test/java/org/springframework/integration/mqtt/Mqttv5BackToBackTests.java index 1fc4a34e76..f6e58501f5 100644 --- a/spring-integration-mqtt/src/test/java/org/springframework/integration/mqtt/Mqttv5BackToBackTests.java +++ b/spring-integration-mqtt/src/test/java/org/springframework/integration/mqtt/Mqttv5BackToBackTests.java @@ -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";