/*
* 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 org.eclipse.paho.client.mqttv3.IMqttAsyncClient;
import org.eclipse.paho.client.mqttv3.IMqttDeliveryToken;
import org.eclipse.paho.client.mqttv3.MqttCallback;
import org.eclipse.paho.client.mqttv3.MqttConnectOptions;
import org.eclipse.paho.client.mqttv3.MqttException;
import org.eclipse.paho.client.mqttv3.MqttMessage;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.ApplicationEventPublisherAware;
import org.springframework.integration.mqtt.core.DefaultMqttPahoClientFactory;
import org.springframework.integration.mqtt.core.MqttPahoClientFactory;
import org.springframework.integration.mqtt.event.MqttMessageDeliveredEvent;
import org.springframework.integration.mqtt.event.MqttMessageSentEvent;
import org.springframework.integration.mqtt.support.MqttMessageConverter;
import org.springframework.messaging.Message;
import org.springframework.messaging.MessagingException;
import org.springframework.util.Assert;
/**
* Eclipse Paho implementation.
*
* @author Gary Russell
* @author Artem Bilan
* @since 4.0
*
*/
public class MqttPahoMessageHandler extends AbstractMqttMessageHandler
implements MqttCallback, ApplicationEventPublisherAware {
private static final int DEFAULT_COMPLETION_TIMEOUT = 30000;
private volatile int completionTimeout = DEFAULT_COMPLETION_TIMEOUT;
private final MqttPahoClientFactory clientFactory;
private volatile IMqttAsyncClient client;
private volatile boolean async;
private volatile boolean asyncEvents;
private volatile ApplicationEventPublisher applicationEventPublisher;
/**
* Use this constructor for a single url (although it may be overridden
* if the server URI(s) are provided by the {@link MqttConnectOptions#getServerURIs()}
* provided by the {@link MqttPahoClientFactory}).
* @param url the URL.
* @param clientId The client id.
* @param clientFactory The client factory.
*/
public MqttPahoMessageHandler(String url, String clientId, MqttPahoClientFactory clientFactory) {
super(url, clientId);
this.clientFactory = clientFactory;
}
/**
* Use this constructor if the server URI(s) are provided by the {@link MqttConnectOptions#getServerURIs()}
* provided by the {@link MqttPahoClientFactory}.
* @param clientId The client id.
* @param clientFactory The client factory.
* @since 4.1
*/
public MqttPahoMessageHandler(String clientId, MqttPahoClientFactory clientFactory) {
super(null, clientId);
this.clientFactory = clientFactory;
}
/**
* Use this URL when you don't need additional {@link MqttConnectOptions}.
* @param url The URL.
* @param clientId The client id.
*/
public MqttPahoMessageHandler(String url, String clientId) {
this(url, clientId, new DefaultMqttPahoClientFactory());
}
/**
* Set to true if you don't want to block when sending messages. Default false.
* When true, message sent/delivered events will be published for reception
* by a suitably configured 'ApplicationListener' or an event
* inbound-channel-adapter.
* @param async true for async.
* @since 4.1
*/
public void setAsync(boolean async) {
this.async = async;
}
/**
* When {@link #setAsync(boolean)} is true, setting this to true enables
* publication of {@link MqttMessageSentEvent} and {@link MqttMessageDeliveredEvent}
* to be emitted. Default false.
* @param asyncEvents the asyncEvents.
* @since 4.1
*/
public void setAsyncEvents(boolean asyncEvents) {
this.asyncEvents = asyncEvents;
}
/**
* Set the completion timeout for async operations. Not settable using the namespace.
* Default 30000 milliseconds.
* @param completionTimeout The timeout.
* @since 4.1
*/
public void setCompletionTimeout(int completionTimeout) {
this.completionTimeout = completionTimeout;
}
@Override
public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) {
this.applicationEventPublisher = applicationEventPublisher;
}
@Override
protected void onInit() throws Exception {
super.onInit();
Assert.state(getConverter() instanceof MqttMessageConverter,
"MessageConverter must be an MqttMessageConverter");
}
@Override
protected void doStart() {
}
@Override
protected void doStop() {
try {
IMqttAsyncClient client = this.client;
if (client != null) {
client.disconnect().waitForCompletion(this.completionTimeout);
client.close();
this.client = null;
}
}
catch (MqttException e) {
logger.error("Failed to disconnect", e);
}
}
private synchronized IMqttAsyncClient checkConnection() throws MqttException {
if (this.client != null && !this.client.isConnected()) {
this.client.close();
this.client = null;
}
if (this.client == null) {
try {
MqttConnectOptions connectionOptions = this.clientFactory.getConnectionOptions();
Assert.state(this.getUrl() != null || connectionOptions.getServerURIs() != null,
"If no 'url' provided, connectionOptions.getServerURIs() must not be null");
IMqttAsyncClient client = this.clientFactory.getAsyncClientInstance(this.getUrl(), this.getClientId());
incrementClientInstance();
client.setCallback(this);
client.connect(connectionOptions).waitForCompletion(this.completionTimeout);
this.client = client;
if (logger.isDebugEnabled()) {
logger.debug("Client connected");
}
}
catch (MqttException e) {
throw new MessagingException("Failed to connect", e);
}
}
return this.client;
}
@Override
protected void publish(String topic, Object mqttMessage, Message<?> message) throws Exception {
Assert.isInstanceOf(MqttMessage.class, mqttMessage);
IMqttAsyncClient client = checkConnection();
IMqttDeliveryToken token = client.publish(topic, (MqttMessage) mqttMessage);
if (!this.async) {
token.waitForCompletion(this.completionTimeout);
}
else if (this.asyncEvents && this.applicationEventPublisher != null) {
this.applicationEventPublisher.publishEvent(
new MqttMessageSentEvent(this, message, topic, token.getMessageId(), getClientId(),
getClientInstance()));
}
}
private void sendDeliveryComplete(IMqttDeliveryToken token) {
if (this.async && this.asyncEvents && this.applicationEventPublisher != null) {
this.applicationEventPublisher.publishEvent(
new MqttMessageDeliveredEvent(this, token.getMessageId(), getClientId(),
getClientInstance()));
}
}
@Override
public synchronized void connectionLost(Throwable cause) {
logger.error("Lost connection; will attempt reconnect on next request");
this.client = null;
}
@Override
public void messageArrived(String topic, MqttMessage message) throws Exception {
}
@Override
public void deliveryComplete(IMqttDeliveryToken token) {
sendDeliveryComplete(token);
}
}