/******************************************************************************* * Copyright (C) 2014, International Business Machines Corporation * All Rights Reserved *******************************************************************************/ package com.ibm.streamsx.messaging.mqtt; import java.net.URI; import java.net.URISyntaxException; import java.util.ArrayList; import java.util.Iterator; import org.apache.log4j.Logger; import org.eclipse.paho.client.mqttv3.IMqttDeliveryToken; import org.eclipse.paho.client.mqttv3.IMqttToken; import org.eclipse.paho.client.mqttv3.MqttAsyncClient; 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.eclipse.paho.client.mqttv3.MqttSecurityException; import org.eclipse.paho.client.mqttv3.persist.MemoryPersistence; import com.ibm.streams.operator.log4j.LoggerNames; import com.ibm.streams.operator.log4j.TraceLevel; import com.ibm.streamsx.messaging.mqtt.Messages; public class MqttAsyncClientWrapper implements MqttCallback{ private static final int COMMAND_TIMEOUT = 5000; private static final Logger TRACE = Logger.getLogger(MqttAsyncClientWrapper.class); private static final Logger LOG = Logger.getLogger(LoggerNames.LOG_FACILITY + "." + MqttAsyncClientWrapper.class.getName()); //$NON-NLS-1$ private String brokerUri; private MqttAsyncClient mqttClient; private MqttConnectOptions conOpt; private ArrayList<MqttCallback> callBackListeners; private float period = 5000; private int reconnectionBound = 5; private boolean shutdown; public MqttAsyncClientWrapper() { conOpt = new MqttConnectOptions(); conOpt.setCleanSession(true); conOpt.setKeepAliveInterval(0); callBackListeners = new ArrayList<MqttCallback>(); } public void setBrokerUri(String brokerUri) throws URISyntaxException { TRACE.log(TraceLevel.DEBUG,"SetBrokerUri: " + brokerUri); //$NON-NLS-1$ URI uri = new URI(brokerUri); // default to tcp:// if no scheme is specified if (uri.getScheme() == null) { brokerUri = "tcp://" + brokerUri; //$NON-NLS-1$ } this.brokerUri = brokerUri; } public String getBrokerUri() { return brokerUri; } public void connect() { MemoryPersistence dataStore = new MemoryPersistence(); try { String clientId = MqttAsyncClient.generateClientId(); mqttClient = new MqttAsyncClient(this.brokerUri,clientId, dataStore); TRACE.log(TraceLevel.DEBUG,"Connect: " + brokerUri); //$NON-NLS-1$ IMqttToken mqttToken = mqttClient.connect(conOpt); mqttToken.waitForCompletion(10000); if (mqttToken.isComplete()) { if (mqttToken.getException() != null) { // TODO: retry } } mqttClient.setCallback(this); } catch (MqttException e) { e.printStackTrace(); // TODO: Log System.exit(1); } } public void connect(int reconnectionBound, float period) throws InterruptedException, MqttException { this.reconnectionBound = reconnectionBound; this.period = period; MemoryPersistence dataStore = new MemoryPersistence(); String clientId = MqttAsyncClient.generateClientId(); TRACE.log(TraceLevel.INFO, "[Connect:]" + brokerUri); //$NON-NLS-1$ TRACE.log(TraceLevel.INFO, "[Connect:] reconnectBound:" + reconnectionBound); //$NON-NLS-1$ TRACE.log(TraceLevel.INFO, "[Connect:] period:" + period); //$NON-NLS-1$ // Construct a non-blocking MQTT client instance mqttClient = new MqttAsyncClient(this.brokerUri, clientId, dataStore); if (reconnectionBound >= 0) { for (int i = 0; i < reconnectionBound && !shutdown; i++) { boolean success = doConnectToServer(period, i); if (success) break; } } else { // this will be an infinite loop for (int i = 0; !shutdown; i++) { boolean success = doConnectToServer(period, i); if (success) break; } } if (mqttClient.isConnected()) { mqttClient.setCallback(this); } else { throw new RuntimeException("Unable to connect to server: " //$NON-NLS-1$ + brokerUri); } } /** * Connect to server, retrun true if successful, false otherwise * @param period * @param i * @return * @throws InterruptedException */ private boolean doConnectToServer(float period, int i) throws InterruptedException { IMqttToken mqttToken = null; try { TRACE.log(TraceLevel.DEBUG, "[Connect:] " + brokerUri + " Attempt: " + i); //$NON-NLS-1$ //$NON-NLS-2$ mqttToken = mqttClient.connect(conOpt); mqttToken.waitForCompletion(COMMAND_TIMEOUT); } catch (MqttSecurityException e) { TRACE.log(TraceLevel.ERROR, Messages.getString("UNABLE_TO_CONNECT_TO_SERVER_CLIENT_WRAPPER"), e); //$NON-NLS-1$ LOG.log(TraceLevel.ERROR, Messages.getString("UNABLE_TO_CONNECT_TO_SERVER_CLIENT_WRAPPER"), e); //$NON-NLS-1$ } catch (MqttException e) { TRACE.log(TraceLevel.ERROR,Messages.getString("UNABLE_TO_CONNECT_TO_SERVER_CLIENT_WRAPPER"), e); //$NON-NLS-1$ LOG.log(TraceLevel.ERROR, Messages.getString("UNABLE_TO_CONNECT_TO_SERVER_CLIENT_WRAPPER"), e); //$NON-NLS-1$ } if (mqttToken.isComplete() && mqttToken.getException() == null && mqttClient.isConnected()) { TRACE.log(TraceLevel.DEBUG, "[Connect:] Success: " + " Attempt: " + i); //$NON-NLS-1$ //$NON-NLS-2$ return true; } else { TRACE.log(TraceLevel.DEBUG, "[Connect:] Fail: " + " Attempt: " + i + " Sleep (ms): " + period); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ Thread.sleep((long) period); return false; } } /** * Publish / send a message to an MQTT server * @param topicName the name of the topic to publish to * @param qos the quality of service to delivery the message at (0,1,2) * @param payload the set of bytes to send to the MQTT server * @throws MqttException * @throws InterruptedException */ public void publish(String topicName, int qos, byte[] payload, boolean retain) throws MqttException, InterruptedException { // Construct the message to send MqttMessage message = new MqttMessage(payload); message.setQos(qos); message.setRetained(retain); if (mqttClient != null && mqttClient.isConnected()) { try { mqttClient.publish(topicName, message, null, null); } catch (Exception e) { if (!mqttClient.isConnected()) { // make sure this client is disconnected disconnect(); connect(reconnectionBound, period); // publish if (mqttClient.isConnected()) { mqttClient.publish(topicName, message, null, null); } } } } else if (mqttClient != null){ // make sure this client is disconnected disconnect(); connect(reconnectionBound, period); // publish if (mqttClient.isConnected()) { mqttClient.publish(topicName, message, null, null); } } } public void subscribe(String[] topics, int[] qos) throws MqttException { if (topics.length != qos.length) { throw new RuntimeException(Messages.getString("NUMBER_OF_TOPICS_MUST_EQUAL_QOS_ENTRIES")); //$NON-NLS-1$ } if (TRACE.getLevel() == TraceLevel.INFO) { for (int i : qos) { String msg = "Topic: " + topics[i] + " / qos: " + qos[i]; //$NON-NLS-1$ //$NON-NLS-2$ TRACE.log(TraceLevel.INFO, msg); } } IMqttToken mqttToken = mqttClient.subscribe(topics, qos); mqttToken.waitForCompletion(COMMAND_TIMEOUT); if (mqttToken.isComplete()) { if (mqttToken.getException() != null) { TRACE.log(TraceLevel.ERROR, "Topics Subscription failed.", mqttToken.getException()); //$NON-NLS-1$ throw mqttToken.getException(); } } } public void disconnect() { TRACE.log(TraceLevel.INFO, "[Disconnect:] " + brokerUri); //$NON-NLS-1$ try { IMqttToken mqttToken = mqttClient.disconnect(); mqttToken.waitForCompletion(10000); } catch (MqttException e) { TRACE.log(TraceLevel.ERROR, e); } } public void addCallBack(MqttCallback callback) { callBackListeners.add(callback); } public void removeCallBack(MqttCallback callback) { callBackListeners.remove(callback); } @Override public void connectionLost(Throwable cause) { TRACE.log(TraceLevel.WARN, "Connection lost: " + brokerUri); //$NON-NLS-1$ for (Iterator iterator = callBackListeners.iterator(); iterator.hasNext();) { MqttCallback callbackListener = (MqttCallback) iterator.next(); callbackListener.connectionLost(cause); } } @Override public void messageArrived(String topic, MqttMessage message) throws Exception { TRACE.log(TraceLevel.TRACE, "[Message Arrived:] topic: " + topic + " qos " + message.getQos() + " message: " + message.getPayload()); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ for (Iterator iterator = callBackListeners.iterator(); iterator.hasNext();) { MqttCallback callbackListener = (MqttCallback) iterator.next(); callbackListener.messageArrived(topic, message); } } @Override public void deliveryComplete(IMqttDeliveryToken token) { TRACE.log(TraceLevel.TRACE, "[Message Delivery Complete:] " + token.getMessageId()); //$NON-NLS-1$ for (Iterator iterator = callBackListeners.iterator(); iterator.hasNext();) { MqttCallback callbackListener = (MqttCallback) iterator.next(); callbackListener.deliveryComplete(token); } } public void shutdown() { TRACE.log(TraceLevel.DEBUG, "[Shutdown async client]"); //$NON-NLS-1$ shutdown = true; } }