/*
* Copyright (c) 2015, WSO2 Inc. (http://www.wso2.org) All Rights Reserved.
*
* WSO2 Inc. licenses this file to you 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.wso2.carbon.device.mgt.iot.agent.kura.firealarm.core.communication.mqtt;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.eclipse.paho.client.mqttv3.IMqttDeliveryToken;
import org.eclipse.paho.client.mqttv3.MqttCallback;
import org.eclipse.paho.client.mqttv3.MqttClient;
import org.eclipse.paho.client.mqttv3.MqttConnectOptions;
import org.eclipse.paho.client.mqttv3.MqttException;
import org.eclipse.paho.client.mqttv3.MqttMessage;
import org.eclipse.paho.client.mqttv3.MqttSecurityException;
import org.wso2.carbon.device.mgt.iot.agent.kura.firealarm.core.communication.CommunicationHandler;
import org.wso2.carbon.device.mgt.iot.agent.kura.firealarm.core.communication
.CommunicationHandlerException;
import java.io.File;
import java.nio.charset.StandardCharsets;
/**
* This class contains the IoT-Server specific implementation for all the MQTT functionality.
* This includes connecting to a MQTT Broker & subscribing to the appropriate MQTT-topic, action
* plan upon losing connection or successfully delivering a message to the broker and processing
* incoming messages. Makes use of the 'Paho-MQTT' library provided by Eclipse Org.
* <p/>
* It is an abstract class that implements the common interface "CommunicationHandler" and the
* "MqttCallback". Whilst providing some methods which handle key MQTT relevant tasks, this class
* implements only the most generic methods of the "CommunicationHandler" interface. The rest of
* the methods are left for any extended concrete-class to implement as per its need.
*/
public abstract class MQTTCommunicationHandler
implements MqttCallback, CommunicationHandler<MqttMessage> {
private static final Log log = LogFactory.getLog(MQTTCommunicationHandler.class);
public static final int DEFAULT_MQTT_QUALITY_OF_SERVICE = 0;
private MqttClient client;
private String clientId;
private MqttConnectOptions options;
private String clientWillTopic;
protected String mqttBrokerEndPoint;
protected int timeoutInterval;
protected String subscribeTopic;
/**
* Constructor for the MQTTCommunicationHandler which takes in the owner, type of the device
* and the MQTT Broker URL and the topic to subscribe.
*
* @param deviceOwner the owner of the device.
* @param deviceType the CDMF Device-Type of the device.
* @param mqttBrokerEndPoint the IP/URL of the MQTT broker endpoint.
* @param subscribeTopic the MQTT topic to which the client is to be subscribed
*/
protected MQTTCommunicationHandler(String deviceOwner, String deviceType,
String mqttBrokerEndPoint,
String subscribeTopic) {
this.clientId = deviceOwner + ":" + deviceType;
this.subscribeTopic = subscribeTopic;
this.clientWillTopic = deviceType + File.separator + "disconnection";
this.mqttBrokerEndPoint = mqttBrokerEndPoint;
this.timeoutInterval = DEFAULT_TIMEOUT_INTERVAL;
this.initSubscriber();
}
/**
* Constructor for the MQTTCommunicationHandler which takes in the owner, type of the device
* and the MQTT Broker URL and the topic to subscribe. Additionally this constructor takes in
* the reconnection-time interval between successive attempts to connect to the broker.
*
* @param deviceOwner the owner of the device.
* @param deviceType the CDMF Device-Type of the device.
* @param mqttBrokerEndPoint the IP/URL of the MQTT broker endpoint.
* @param subscribeTopic the MQTT topic to which the client is to be subscribed
* @param intervalInMillis the time interval in MILLI-SECONDS between successive
* attempts to connect to the broker.
*/
protected MQTTCommunicationHandler(String deviceOwner, String deviceType,
String mqttBrokerEndPoint, String subscribeTopic,
int intervalInMillis) {
this.clientId = deviceOwner + ":" + deviceType;
this.subscribeTopic = subscribeTopic;
this.clientWillTopic = deviceType + File.separator + "disconnection";
this.mqttBrokerEndPoint = mqttBrokerEndPoint;
this.timeoutInterval = intervalInMillis;
this.initSubscriber();
}
public void setTimeoutInterval(int timeoutInterval) {
this.timeoutInterval = timeoutInterval;
}
/**
* Initializes the MQTT-Client. Creates a client using the given MQTT-broker endpoint and the
* clientId (which is constructed by a concatenation of [deviceOwner]:[deviceType]). Also sets
* the client's options parameter with the clientWillTopic (in-case of connection failure) and
* other info. Also sets the call-back this current class.
*/
private void initSubscriber() {
try {
client = new MqttClient(this.mqttBrokerEndPoint, clientId, null);
log.info("MQTT subscriber was created with ClientID : " + clientId);
} catch (MqttException ex) {
String errorMsg = "MQTT Client Error\n" + "\tReason: " + ex.getReasonCode() +
"\n\tMessage: " + ex.getMessage() + "\n\tLocalMsg: " +
ex.getLocalizedMessage() + "\n\tCause: " + ex.getCause() +
"\n\tException: " + ex;
log.error(errorMsg);
}
options = new MqttConnectOptions();
options.setCleanSession(false);
options.setWill(clientWillTopic, "Connection-Lost".getBytes(StandardCharsets.UTF_8), 2,
true);
client.setCallback(this);
}
/**
* Checks whether the connection to the MQTT-Broker persists.
*
* @return true if the client is connected to the MQTT-Broker, else false.
*/
@Override
public boolean isConnected() {
return client.isConnected();
}
/**
* Connects to the MQTT-Broker and if successfully established connection.
*
* @throws CommunicationHandlerException in the event of 'Connecting to' the MQTT broker fails.
*/
protected void connectToQueue() throws CommunicationHandlerException {
try {
client.connect(options);
if (log.isDebugEnabled()) {
log.debug("Subscriber connected to queue at: " + this.mqttBrokerEndPoint);
}
} catch (MqttSecurityException ex) {
String errorMsg = "MQTT Security Exception when connecting to queue\n" + "\tReason: " +
" " +
ex.getReasonCode() + "\n\tMessage: " + ex.getMessage() +
"\n\tLocalMsg: " + ex.getLocalizedMessage() + "\n\tCause: " +
ex.getCause() + "\n\tException: " + ex;
if (log.isDebugEnabled()) {
log.debug(errorMsg);
}
throw new CommunicationHandlerException(errorMsg, ex);
} catch (MqttException ex) {
String errorMsg = "MQTT Exception when connecting to queue\n" + "\tReason: " +
ex.getReasonCode() + "\n\tMessage: " + ex.getMessage() +
"\n\tLocalMsg: " + ex.getLocalizedMessage() + "\n\tCause: " +
ex.getCause() + "\n\tException: " + ex;
if (log.isDebugEnabled()) {
log.debug(errorMsg);
}
throw new CommunicationHandlerException(errorMsg, ex);
}
}
/**
* Subscribes to the MQTT-Topic specific to this MQTTClient. (The MQTT-Topic specific to the
* device is taken in as a constructor parameter of this class) .
*
* @throws CommunicationHandlerException in the event of 'Subscribing to' the MQTT broker
* fails.
*/
protected void subscribeToQueue() throws CommunicationHandlerException {
try {
client.subscribe(subscribeTopic, 0);
log.info("Subscriber '" + clientId + "' subscribed to topic: " + subscribeTopic);
} catch (MqttException ex) {
String errorMsg = "MQTT Exception when trying to subscribe to topic: " +
subscribeTopic + "\n\tReason: " + ex.getReasonCode() +
"\n\tMessage: " + ex.getMessage() + "\n\tLocalMsg: " +
ex.getLocalizedMessage() + "\n\tCause: " + ex.getCause() +
"\n\tException: " + ex;
if (log.isDebugEnabled()) {
log.debug(errorMsg);
}
throw new CommunicationHandlerException(errorMsg, ex);
}
}
/**
* This method is used to publish reply-messages for the control signals received.
* Invocation of this method calls its overloaded-method with a QoS equal to that of the
* default value.
*
* @param topic the topic to which the reply message is to be published.
* @param payLoad the reply-message (payload) of the MQTT publish action.
*/
protected void publishToQueue(String topic, String payLoad)
throws CommunicationHandlerException {
publishToQueue(topic, payLoad, DEFAULT_MQTT_QUALITY_OF_SERVICE, true);
}
/**
* This is an overloaded method that publishes MQTT reply-messages for control signals
* received form the IoT-Server.
*
* @param topic the topic to which the reply message is to be published
* @param payLoad the reply-message (payload) of the MQTT publish action.
* @param qos the Quality-of-Service of the current publish action.
* Could be 0(At-most once), 1(At-least once) or 2(Exactly once)
*/
protected void publishToQueue(String topic, String payLoad, int qos, boolean retained)
throws CommunicationHandlerException {
try {
client.publish(topic, payLoad.getBytes(StandardCharsets.UTF_8), qos, retained);
if (log.isDebugEnabled()) {
log.debug("Message: " + payLoad + " to MQTT topic [" + topic +
"] published successfully");
}
} catch (MqttException ex) {
String errorMsg =
"MQTT Client Error" + "\n\tReason: " + ex.getReasonCode() + "\n\tMessage: " +
ex.getMessage() + "\n\tLocalMsg: " + ex.getLocalizedMessage() +
"\n\tCause: " + ex.getCause() + "\n\tException: " + ex;
log.info(errorMsg);
throw new CommunicationHandlerException(errorMsg, ex);
}
}
protected void publishToQueue(String topic, MqttMessage message)
throws CommunicationHandlerException {
try {
client.publish(topic, message);
if (log.isDebugEnabled()) {
log.debug("Message: " + message.toString() + " to MQTT topic [" + topic +
"] published successfully");
}
} catch (MqttException ex) {
String errorMsg =
"MQTT Client Error" + "\n\tReason: " + ex.getReasonCode() + "\n\tMessage: " +
ex.getMessage() + "\n\tLocalMsg: " + ex.getLocalizedMessage() +
"\n\tCause: " + ex.getCause() + "\n\tException: " + ex;
log.info(errorMsg);
throw new CommunicationHandlerException(errorMsg, ex);
}
}
/**
* Callback method which is triggered once the MQTT client losers its connection to the broker.
* Spawns a new thread that executes necessary actions to try and reconnect to the endpoint.
*
* @param throwable a Throwable Object containing the details as to why the failure occurred.
*/
@Override
public void connectionLost(Throwable throwable) {
log.warn("Lost Connection for client: " + this.clientId +
" to " + this.mqttBrokerEndPoint + ".\nThis was due to - " +
throwable.getMessage());
Thread reconnectThread = new Thread() {
public void run() {
connect();
}
};
reconnectThread.setDaemon(true);
reconnectThread.start();
}
/**
* Callback method which is triggered upon receiving a MQTT Message from the broker. Spawns a
* new thread that executes any actions to be taken with the received message.
*
* @param topic the MQTT-Topic to which the received message was published to and the
* client was subscribed to.
* @param mqttMessage the actual MQTT-Message that was received from the broker.
*/
@Override
public void messageArrived(final String topic, final MqttMessage mqttMessage) {
if (log.isDebugEnabled()) {
log.info("Got an MQTT message '" + mqttMessage.toString() + "' for topic '" + topic +
"'.");
}
Thread messageProcessorThread = new Thread() {
public void run() {
processIncomingMessage(mqttMessage);
}
};
messageProcessorThread.setDaemon(true);
messageProcessorThread.start();
}
/**
* Callback method which gets triggered upon successful completion of a message delivery to
* the broker.
*
* @param iMqttDeliveryToken the MQTT-DeliveryToken which includes the details about the
* specific message delivery.
*/
@Override
public void deliveryComplete(IMqttDeliveryToken iMqttDeliveryToken) {
String message = "";
try {
message = iMqttDeliveryToken.getMessage().toString();
} catch (MqttException e) {
log.error(
"Error occurred whilst trying to read the message from the MQTT delivery " +
"token.");
}
String topic = iMqttDeliveryToken.getTopics()[0];
String client = iMqttDeliveryToken.getClient().getClientId();
if (log.isDebugEnabled()) {
log.debug("Message - '" + message + "' of client [" + client + "] for the topic (" +
topic +
") was delivered successfully.");
}
}
/**
* Closes the connection to the MQTT Broker.
*/
public void closeConnection() throws MqttException {
if (client != null && isConnected()) {
client.disconnect();
}
}
}