From bdf8bb56ecc5d4e9b9cca3ee7771413d2056b20c Mon Sep 17 00:00:00 2001 From: Soby Chacko Date: Tue, 19 May 2020 15:42:22 -0400 Subject: [PATCH] Mqtt sink * MQTT Consumer/Sink Resolves https://github.com/spring-cloud/stream-applications/issues/41 --- consumer/mqtt-consumer/pom.xml | 52 ++++++++ .../mqtt/MqttConsumerConfiguration.java | 75 +++++++++++ .../consumer/mqtt/MqttConsumerProperties.java | 118 ++++++++++++++++++ .../fn/consumer/mqtt/MqttConsumerTests.java | 97 ++++++++++++++ pom.xml | 4 + 5 files changed, 346 insertions(+) create mode 100644 consumer/mqtt-consumer/pom.xml create mode 100644 consumer/mqtt-consumer/src/main/java/org/springframework/cloud/fn/consumer/mqtt/MqttConsumerConfiguration.java create mode 100644 consumer/mqtt-consumer/src/main/java/org/springframework/cloud/fn/consumer/mqtt/MqttConsumerProperties.java create mode 100644 consumer/mqtt-consumer/src/test/java/org/springframework/cloud/fn/consumer/mqtt/MqttConsumerTests.java diff --git a/consumer/mqtt-consumer/pom.xml b/consumer/mqtt-consumer/pom.xml new file mode 100644 index 00000000..e1a540ab --- /dev/null +++ b/consumer/mqtt-consumer/pom.xml @@ -0,0 +1,52 @@ + + + 4.0.0 + mqtt-consumer + 1.0.0-SNAPSHOT + mqtt-consumer + mqtt consumer + + + org.springframework.cloud.fn + spring-functions-parent + 1.0.0-SNAPSHOT + ../../spring-functions-parent + + + + + org.springframework.cloud.fn + mqtt-common + ${project.version} + + + org.springframework.boot + spring-boot-starter-integration + + + org.springframework.boot + spring-boot-starter-validation + + + org.springframework.boot + spring-boot-configuration-processor + provided + + + org.springframework.boot + spring-boot-starter-test + test + + + io.projectreactor + reactor-test + test + + + org.testcontainers + testcontainers + 1.9.1 + test + + + diff --git a/consumer/mqtt-consumer/src/main/java/org/springframework/cloud/fn/consumer/mqtt/MqttConsumerConfiguration.java b/consumer/mqtt-consumer/src/main/java/org/springframework/cloud/fn/consumer/mqtt/MqttConsumerConfiguration.java new file mode 100644 index 00000000..85a69726 --- /dev/null +++ b/consumer/mqtt-consumer/src/main/java/org/springframework/cloud/fn/consumer/mqtt/MqttConsumerConfiguration.java @@ -0,0 +1,75 @@ +/* + * Copyright 2017-2020 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. + * You may obtain a copy of the License at + * + * https://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.springframework.cloud.fn.consumer.mqtt; + +import java.util.function.Consumer; + +import org.springframework.beans.factory.BeanFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.cloud.fn.common.mqtt.MqttConfiguration; +import org.springframework.cloud.fn.common.mqtt.MqttProperties; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; +import org.springframework.integration.mqtt.core.MqttPahoClientFactory; +import org.springframework.integration.mqtt.outbound.MqttPahoMessageHandler; +import org.springframework.integration.mqtt.support.DefaultPahoMessageConverter; +import org.springframework.messaging.Message; +import org.springframework.messaging.MessageHandler; + +/** + * A consumer that sends data to Mqtt. + * + * @author Janne Valkealahti + * + */ +@Configuration +@EnableConfigurationProperties({ MqttProperties.class, MqttConsumerProperties.class }) +@Import(MqttConfiguration.class) +public class MqttConsumerConfiguration { + + @Autowired + private MqttConsumerProperties properties; + + @Autowired + private MqttPahoClientFactory mqttClientFactory; + + @Autowired + private BeanFactory beanFactory; + + @Bean + public Consumer> mqttConsumer() { + return mqttOutbound()::handleMessage; + } + + @Bean + public MessageHandler mqttOutbound() { + MqttPahoMessageHandler messageHandler = new MqttPahoMessageHandler(properties.getClientId(), mqttClientFactory); + messageHandler.setAsync(properties.isAsync()); + messageHandler.setDefaultTopic(properties.getTopic()); + messageHandler.setConverter(pahoMessageConverter()); + return messageHandler; + } + + public DefaultPahoMessageConverter pahoMessageConverter() { + DefaultPahoMessageConverter converter = new DefaultPahoMessageConverter(properties.getQos(), + properties.isRetained(), properties.getCharset()); + converter.setBeanFactory(beanFactory); + return converter; + } +} diff --git a/consumer/mqtt-consumer/src/main/java/org/springframework/cloud/fn/consumer/mqtt/MqttConsumerProperties.java b/consumer/mqtt-consumer/src/main/java/org/springframework/cloud/fn/consumer/mqtt/MqttConsumerProperties.java new file mode 100644 index 00000000..7b60ad66 --- /dev/null +++ b/consumer/mqtt-consumer/src/main/java/org/springframework/cloud/fn/consumer/mqtt/MqttConsumerProperties.java @@ -0,0 +1,118 @@ +/* + * Copyright 2017-2020 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. + * You may obtain a copy of the License at + * + * https://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.springframework.cloud.fn.consumer.mqtt; + +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.Size; + +import org.hibernate.validator.constraints.Range; + +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.validation.annotation.Validated; + +/** + * Properties for the Mqtt Consumer. + * + * @author Janne Valkealahti + * + */ +@Validated +@ConfigurationProperties("mqtt.consumer") +public class MqttConsumerProperties { + + /** + * identifies the client. + */ + private String clientId = "stream.client.id.sink"; + + /** + * the topic to which the sink will publish. + */ + private String topic = "stream.mqtt"; + + /** + * the quality of service to use. + */ + private int qos = 1; + + /** + * whether to set the 'retained' flag. + */ + private boolean retained = false; + + /** + * the charset used to convert a String payload to byte[]. + */ + private String charset = "UTF-8"; + + /** + * whether or not to use async sends. + */ + private boolean async = false; + + @Range(min = 0, max = 2) + public int getQos() { + return this.qos; + } + + public boolean isRetained() { + return this.retained; + } + + public void setQos(int qos) { + this.qos = qos; + } + + public void setRetained(boolean retained) { + this.retained = retained; + } + + @NotBlank + @Size(min = 1, max = 23) + public String getClientId() { + return this.clientId; + } + + public void setClientId(String clientId) { + this.clientId = clientId; + } + + @NotBlank + public String getTopic() { + return this.topic; + } + + public void setTopic(String topic) { + this.topic = topic; + } + + public String getCharset() { + return this.charset; + } + + public void setCharset(String charset) { + this.charset = charset; + } + + public boolean isAsync() { + return this.async; + } + + public void setAsync(boolean async) { + this.async = async; + } +} diff --git a/consumer/mqtt-consumer/src/test/java/org/springframework/cloud/fn/consumer/mqtt/MqttConsumerTests.java b/consumer/mqtt-consumer/src/test/java/org/springframework/cloud/fn/consumer/mqtt/MqttConsumerTests.java new file mode 100644 index 00000000..a5cd3608 --- /dev/null +++ b/consumer/mqtt-consumer/src/test/java/org/springframework/cloud/fn/consumer/mqtt/MqttConsumerTests.java @@ -0,0 +1,97 @@ +/* + * Copyright 2017-2020 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. + * You may obtain a copy of the License at + * + * https://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.springframework.cloud.fn.consumer.mqtt; + +import java.util.function.Consumer; + +import com.github.dockerjava.api.command.CreateContainerCmd; +import com.github.dockerjava.api.model.ExposedPort; +import com.github.dockerjava.api.model.PortBinding; +import com.github.dockerjava.api.model.Ports; +import org.junit.jupiter.api.Test; +import org.testcontainers.containers.GenericContainer; + +import org.springframework.beans.factory.BeanFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.context.annotation.Bean; +import org.springframework.integration.channel.QueueChannel; +import org.springframework.integration.mqtt.core.MqttPahoClientFactory; +import org.springframework.integration.mqtt.inbound.MqttPahoMessageDrivenChannelAdapter; +import org.springframework.integration.mqtt.support.DefaultPahoMessageConverter; +import org.springframework.messaging.Message; +import org.springframework.messaging.support.MessageBuilder; +import org.springframework.test.annotation.DirtiesContext; + +import static org.assertj.core.api.Assertions.assertThat; + +@SpringBootTest(properties = "mqtt.consumer.topic=test") +@DirtiesContext +public class MqttConsumerTests { + + static { + Consumer cmd = e -> e.withPortBindings(new PortBinding(Ports.Binding.bindPort(1883), new ExposedPort(1883))); + GenericContainer mosquitto = new GenericContainer("eclipse-mosquitto") + .withExposedPorts(1883) + .withCreateContainerCmdModifier(cmd); + mosquitto.start(); + } + + @Autowired + private Consumer> mqttConsumer; + + @Autowired + protected QueueChannel queue; + + @Test + public void testMqttConsumer() { + this.mqttConsumer.accept(MessageBuilder.withPayload("hello").build()); + Message in = this.queue.receive(10000); + assertThat(in).isNotNull(); + assertThat(in.getPayload()).isEqualTo("hello"); + } + + @SpringBootApplication + static class TestApplication { + + @Autowired + private MqttPahoClientFactory mqttClientFactory; + + @Bean + public MqttPahoMessageDrivenChannelAdapter mqttInbound(BeanFactory beanFactory) { + MqttPahoMessageDrivenChannelAdapter adapter = new MqttPahoMessageDrivenChannelAdapter("test", + mqttClientFactory, "test"); + adapter.setQos(0); + adapter.setConverter(pahoMessageConverter(beanFactory)); + adapter.setOutputChannelName("queue"); + return adapter; + } + + public DefaultPahoMessageConverter pahoMessageConverter(BeanFactory beanFactory) { + DefaultPahoMessageConverter converter = new DefaultPahoMessageConverter(1, true, "UTF-8"); + converter.setPayloadAsBytes(false); + converter.setBeanFactory(beanFactory); + return converter; + } + + @Bean + public QueueChannel queue() { + return new QueueChannel(); + } + } +} diff --git a/pom.xml b/pom.xml index 44d8cb63..1f921301 100644 --- a/pom.xml +++ b/pom.xml @@ -42,7 +42,10 @@ common/ftp-common common/function-test-support +<<<<<<< HEAD common/tcp-common +======= +>>>>>>> MQTT source as supplier/source common/mqtt-common consumer/cassandra-consumer @@ -52,6 +55,7 @@ consumer/jdbc-consumer consumer/log-consumer consumer/mongodb-consumer + consumer/mqtt-consumer consumer/rabbit-consumer consumer/redis-consumer consumer/sftp-consumer