/* * Copyright 2002-2016 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 * * 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.springframework.integration.mqtt.inbound; import java.util.LinkedHashSet; import java.util.Set; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; import org.springframework.integration.endpoint.MessageProducerSupport; import org.springframework.integration.mqtt.support.DefaultPahoMessageConverter; import org.springframework.integration.mqtt.support.MqttMessageConverter; import org.springframework.integration.support.management.IntegrationManagedResource; import org.springframework.jmx.export.annotation.ManagedAttribute; import org.springframework.jmx.export.annotation.ManagedOperation; import org.springframework.jmx.export.annotation.ManagedResource; import org.springframework.messaging.MessagingException; import org.springframework.util.Assert; /** * Abstract class for MQTT Message-Driven Channel Adapters. * * @author Gary Russell * @since 4.0 * */ @ManagedResource @IntegrationManagedResource public abstract class AbstractMqttMessageDrivenChannelAdapter extends MessageProducerSupport { private final String url; private final String clientId; private final Set<Topic> topics; private volatile MqttMessageConverter converter; protected final Lock topicLock = new ReentrantLock(); public AbstractMqttMessageDrivenChannelAdapter(String url, String clientId, String... topic) { Assert.hasText(clientId, "'clientId' cannot be null or empty"); Assert.notNull(topic, "'topics' cannot be null"); Assert.noNullElements(topic, "'topics' cannot have null elements"); this.url = url; this.clientId = clientId; this.topics = new LinkedHashSet<Topic>(); for (String t : topic) { this.topics.add(new Topic(t, 1)); } } public void setConverter(MqttMessageConverter converter) { Assert.notNull(converter, "'converter' cannot be null"); this.converter = converter; } /** * Set the QoS for each topic; a single value will apply to all topics otherwise * the correct number of qos values must be provided. * @param qos The qos value(s). * @since 4.1 */ public void setQos(int... qos) { Assert.notNull(qos, "'qos' cannot be null"); if (qos.length == 1) { for (Topic topic : this.topics) { topic.setQos(qos[0]); } } else { Assert.isTrue(qos.length == this.topics.size(), "When setting qos, the array must be the same length as the topics"); int n = 0; for (Topic topic : this.topics) { topic.setQos(qos[n++]); } } } @ManagedAttribute public int[] getQos() { this.topicLock.lock(); try { int[] topicQos = new int[this.topics.size()]; int n = 0; for (Topic topic : this.topics) { topicQos[n++] = topic.getQos(); } return topicQos; } finally { this.topicLock.unlock(); } } protected String getUrl() { return this.url; } protected String getClientId() { return this.clientId; } protected MqttMessageConverter getConverter() { return this.converter; } @ManagedAttribute public String[] getTopic() { this.topicLock.lock(); try { String[] topicNames = new String[this.topics.size()]; int n = 0; for (Topic topic : this.topics) { topicNames[n++] = topic.getTopic(); } return topicNames; } finally { this.topicLock.unlock(); } } @Override public String getComponentType() { return "mqtt:inbound-channel-adapter"; } /** * Add a topic to the subscribed list. * @param topic The topic. * @param qos The qos. * @throws MessagingException if the topic is already in the list. * @since 4.1 */ @ManagedOperation public void addTopic(String topic, int qos) { this.topicLock.lock(); try { Topic topik = new Topic(topic, qos); if (this.topics.contains(topik)) { throw new MessagingException("Topic '" + topic + "' is already subscribed."); } this.topics.add(topik); if (this.logger.isDebugEnabled()) { logger.debug("Added '" + topic + "' to subscriptions."); } } finally { this.topicLock.unlock(); } } /** * Add a topic (or topics) to the subscribed list (qos=1). * @param topic The topics. * @throws MessagingException if the topic is already in the list. * @since 4.1 */ @ManagedOperation public void addTopic(String... topic) { Assert.notNull(topic, "'topic' cannot be null"); this.topicLock.lock(); try { for (String t : topic) { addTopic(t, 1); } } finally { this.topicLock.unlock(); } } /** * Add topics to the subscribed list. * @param topic The topics. * @param qos The qos for each topic. * @throws MessagingException if a topic is already in the list. * @since 4.1 */ @ManagedOperation public void addTopics(String[] topic, int[] qos) { Assert.notNull(topic, "'topic' cannot be null."); Assert.noNullElements(topic, "'topic' cannot contain any null elements."); Assert.isTrue(topic.length == qos.length, "topic and qos arrays must the be the same length."); this.topicLock.lock(); try { for (String topik : topic) { if (this.topics.contains(new Topic(topik, 0))) { throw new MessagingException("Topic '" + topik + "' is already subscribed."); } } for (int i = 0; i < topic.length; i++) { addTopic(topic[i], qos[i]); } } finally { this.topicLock.unlock(); } } /** * Remove a topic (or topics) from the subscribed list. * @param topic The topic. * @throws MessagingException if the topic is not in the list. * @since 4.1 */ @ManagedOperation public void removeTopic(String... topic) { this.topicLock.lock(); try { for (String t : topic) { if (this.topics.remove(new Topic(t, 0))) { if (this.logger.isDebugEnabled()) { logger.debug("Removed '" + t + "' from subscriptions."); } } } } finally { this.topicLock.unlock(); } } @Override protected void onInit() { super.onInit(); if (this.converter == null) { DefaultPahoMessageConverter pahoMessageConverter = new DefaultPahoMessageConverter(); pahoMessageConverter.setBeanFactory(getBeanFactory()); this.converter = pahoMessageConverter; } } /** * @since 4.1 */ private static final class Topic { private final String topic; private volatile int qos; Topic(String topic, int qos) { this.topic = topic; this.qos = qos; } private int getQos() { return this.qos; } private void setQos(int qos) { this.qos = qos; } private String getTopic() { return this.topic; } @Override public int hashCode() { return this.topic.hashCode(); } @Override public boolean equals(Object obj) { if (this == obj) { return true; } if (obj == null) { return false; } if (getClass() != obj.getClass()) { 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; } @Override public String toString() { return "Topic [topic=" + this.topic + ", qos=" + this.qos + "]"; } } }