/*
* 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.outbound;
import java.util.concurrent.atomic.AtomicBoolean;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.context.Lifecycle;
import org.springframework.expression.Expression;
import org.springframework.integration.handler.AbstractMessageHandler;
import org.springframework.integration.handler.ExpressionEvaluatingMessageProcessor;
import org.springframework.integration.handler.MessageProcessor;
import org.springframework.integration.mqtt.support.DefaultPahoMessageConverter;
import org.springframework.integration.mqtt.support.MqttHeaders;
import org.springframework.integration.mqtt.support.MqttMessageConverter;
import org.springframework.messaging.Message;
import org.springframework.messaging.MessageHandlingException;
import org.springframework.messaging.converter.MessageConverter;
import org.springframework.util.Assert;
/**
* Abstract class for MQTT outbound channel adapters.
*
* @author Gary Russell
* @author Artem Bilan
* @since 4.0
*
*/
public abstract class AbstractMqttMessageHandler extends AbstractMessageHandler implements Lifecycle {
private static final MessageProcessor<String> DEFAULT_TOPIC_PROCESSOR =
m -> (String) m.getHeaders().get(MqttHeaders.TOPIC);
private final AtomicBoolean running = new AtomicBoolean();
private final String url;
private final String clientId;
private String defaultTopic;
private MessageProcessor<String> topicProcessor = DEFAULT_TOPIC_PROCESSOR;
private int defaultQos = 0;
private MessageProcessor<Integer> qosProcessor = MqttMessageConverter.defaultQosProcessor();
private boolean defaultRetained;
private MessageProcessor<Boolean> retainedProcessor = MqttMessageConverter.defaultRetainedProcessor();
private MessageConverter converter;
private int clientInstance;
public AbstractMqttMessageHandler(String url, String clientId) {
Assert.hasText(clientId, "'clientId' cannot be null or empty");
this.url = url;
this.clientId = clientId;
}
/**
* Set the topic to which the message will be published if the
* {@link #setTopicExpression(Expression) topicExpression} evaluates to `null`.
* @param defaultTopic the default topic.
*/
public void setDefaultTopic(String defaultTopic) {
this.defaultTopic = defaultTopic;
}
/**
* Set the topic expression; default "headers['mqtt_topic']".
* @param topicExpression the expression.
* @since 5.0
*/
public void setTopicExpression(Expression topicExpression) {
Assert.notNull(topicExpression, "'topicExpression' cannot be null");
this.topicProcessor = new ExpressionEvaluatingMessageProcessor<>(topicExpression);
}
/**
* Set the topic expression; default "headers['mqtt_topic']".
* @param topicExpression the expression.
* @since 5.0
*/
public void setTopicExpressionString(String topicExpression) {
Assert.hasText(topicExpression, "'topicExpression' must not be null or empty");
this.topicProcessor = new ExpressionEvaluatingMessageProcessor<>(topicExpression);
}
/**
* Set the qos for messages if the {@link #setQosExpression(Expression) qosExpression}
* evaluates to null. Only applies if a message converter is not provided.
* @param defaultQos the default qos.
* @see #setConverter(MessageConverter)
*/
public void setDefaultQos(int defaultQos) {
this.defaultQos = defaultQos;
}
/**
* Set the qos expression; default "headers['mqtt_qos']".
* Only applies if a message converter is not provided.
* @param qosExpression the expression.
* @see #setConverter(MessageConverter)
* @since 5.0
*/
public void setQosExpression(Expression qosExpression) {
Assert.notNull(qosExpression, "'qosExpression' cannot be null");
this.qosProcessor = new ExpressionEvaluatingMessageProcessor<>(qosExpression);
}
/**
* Set the qos expression; default "headers['mqtt_qos']".
* Only applies if a message converter is not provided.
* @param qosExpression the expression.
* @see #setConverter(MessageConverter)
* @since 5.0
*/
public void setQosExpressionString(String qosExpression) {
Assert.hasText(qosExpression, "'qosExpression' must not be null or empty");
this.qosProcessor = new ExpressionEvaluatingMessageProcessor<>(qosExpression);
}
/**
* Set the retained boolean for messages if the
* {@link #setRetainedExpression(Expression) retainedExpression} evaluates to null.
* Only applies if a message converter is not provided.
* @param defaultRetained the default defaultRetained.
* @see #setConverter(MessageConverter)
*/
public void setDefaultRetained(boolean defaultRetained) {
this.defaultRetained = defaultRetained;
}
/**
* Set the retained expression; default "headers['mqtt_retained']".
* Only applies if a message converter is not provided.
* @param retainedExpression the expression.
* @see #setConverter(MessageConverter)
* @since 5.0
*/
public void setRetainedExpression(Expression retainedExpression) {
Assert.notNull(retainedExpression, "'qosExpression' cannot be null");
this.retainedProcessor = new ExpressionEvaluatingMessageProcessor<>(retainedExpression);
}
/**
* Set the retained expression; default "headers['mqtt_retained']".
* Only applies if a message converter is not provided.
* @param retainedExpression the expression.
* @see #setConverter(MessageConverter)
* @since 5.0
*/
public void setRetainedExpressionString(String retainedExpression) {
Assert.hasText(retainedExpression, "'qosExpression' must not be null or empty");
this.retainedProcessor = new ExpressionEvaluatingMessageProcessor<>(retainedExpression);
}
/**
* Set the message converter to use; if this is provided, the adapter qos and retained
* settings are ignored.
* @param converter the converter.
*/
public void setConverter(MessageConverter converter) {
Assert.notNull(converter, "'converter' cannot be null");
this.converter = converter;
}
protected MessageConverter getConverter() {
return this.converter;
}
protected String getUrl() {
return this.url;
}
public String getClientId() {
return this.clientId;
}
/**
* Incremented each time the client is connected.
* @return The instance;
* @since 4.1
*/
public int getClientInstance() {
return this.clientInstance;
}
@Override
public String getComponentType() {
return "mqtt:outbound-channel-adapter";
}
protected void incrementClientInstance() {
this.clientInstance++; //NOSONAR - false positive - called from synchronized block
}
@Override
protected void onInit() throws Exception {
super.onInit();
if (this.topicProcessor instanceof BeanFactoryAware && getBeanFactory() != null) {
((BeanFactoryAware) this.topicProcessor).setBeanFactory(getBeanFactory());
}
if (this.qosProcessor instanceof BeanFactoryAware && getBeanFactory() != null) {
((BeanFactoryAware) this.qosProcessor).setBeanFactory(getBeanFactory());
}
if (this.retainedProcessor instanceof BeanFactoryAware && getBeanFactory() != null) {
((BeanFactoryAware) this.retainedProcessor).setBeanFactory(getBeanFactory());
}
if (this.converter == null) {
DefaultPahoMessageConverter defaultConverter = new DefaultPahoMessageConverter(this.defaultQos,
this.qosProcessor, this.defaultRetained, this.retainedProcessor);
if (getBeanFactory() != null) {
defaultConverter.setBeanFactory(getBeanFactory());
}
this.converter = defaultConverter;
}
}
@Override
public final void start() {
if (!this.running.getAndSet(true)) {
doStart();
}
}
protected abstract void doStart();
@Override
public final void stop() {
if (this.running.getAndSet(false)) {
doStop();
}
}
protected abstract void doStop();
@Override
public boolean isRunning() {
return this.running.get();
}
@Override
protected void handleMessageInternal(Message<?> message) throws Exception {
Object mqttMessage = this.converter.fromMessage(message, Object.class);
String topic = this.topicProcessor.processMessage(message);
if (topic == null && this.defaultTopic == null) {
throw new MessageHandlingException(message,
"No topic could be determined from the message and no default topic defined");
}
this.publish(topic == null ? this.defaultTopic : topic, mqttMessage, message);
}
protected abstract void publish(String topic, Object mqttMessage, Message<?> message) throws Exception;
}