/******************************************************************************* * Copyright (C) 2014, International Business Machines Corporation * All Rights Reserved *******************************************************************************/ package com.ibm.streamsx.messaging.mqtt; import java.net.URISyntaxException; import java.util.ArrayList; import java.util.Iterator; import java.util.Properties; import org.apache.log4j.Logger; 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.MqttPersistenceException; 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.streams.operator.metrics.Metric; import com.ibm.streamsx.messaging.common.PropertyProvider; import com.ibm.streamsx.messaging.mqtt.Messages; public class MqttClientWrapper 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 static final String EMPTY_STR = ""; //$NON-NLS-1$ private String brokerUri; private MqttClient mqttClient; private MqttConnectOptions conOpt; private String pendingBrokerUri = EMPTY_STR; private ArrayList<MqttCallback> callBackListeners; private long period = 5000; private int reconnectionBound = 5; private String clientID; private long commandTimeout; private boolean shutdown; private Metric nConnectionLost; private Metric isConnected; private PropertyProvider propProvider; private String userPropName; private String passwordPropName; public MqttClientWrapper() { conOpt = new MqttConnectOptions(); conOpt.setCleanSession(true); callBackListeners = new ArrayList<MqttCallback>(); } public void setPropProvider(PropertyProvider propProvider) { this.propProvider = propProvider; } public void setUserPropName(String userPropName) { this.userPropName = userPropName; } public void setPasswordPropName(String passwordPropName) { this.passwordPropName = passwordPropName; } public void setConnectionLostMetric(Metric nConnectionLost) { this.nConnectionLost = nConnectionLost; } public void setIsConnectedMetric(Metric isConnected) { this.isConnected = isConnected; } public void setKeepAliveInterval(int keepAliveInterval) { if(keepAliveInterval >= 0) { conOpt.setKeepAliveInterval(keepAliveInterval); } } public void setCommandTimeout(long commandTimeout) { this.commandTimeout = commandTimeout; } public void setClientID(String clientID) { this.clientID = clientID; } public void setUserID(String userID) { if(userID != null && userID.trim().length() > 0) { conOpt.setUserName(userID); } } public void setPassword(String password) { if(password != null && password.trim().length() > 0) { conOpt.setPassword(password.toCharArray()); } } public void setBrokerUri(String brokerUri) throws URISyntaxException { TRACE.log(TraceLevel.DEBUG,"SetBrokerUri: " + brokerUri); //$NON-NLS-1$ // default to tcp:// if no scheme is specified if (!brokerUri.startsWith("tcp://") && !brokerUri.startsWith("ssl://")) //$NON-NLS-1$ //$NON-NLS-2$ { brokerUri = "tcp://" + brokerUri; //$NON-NLS-1$ } this.brokerUri = brokerUri; } public String getBrokerUri() { return brokerUri; } public void connect() { MemoryPersistence dataStore = new MemoryPersistence(); try { String clientId = newClientId(); mqttClient = new MqttClient(this.brokerUri,clientId, dataStore); mqttClient.setTimeToWait(COMMAND_TIMEOUT); TRACE.log(TraceLevel.DEBUG,"Connect: " + brokerUri); //$NON-NLS-1$ mqttClient.connect(conOpt); mqttClient.setCallback(this); } catch (MqttException e) { e.printStackTrace(); // TODO: Log System.exit(1); } } synchronized public void connect(int reconnectionBound, long period) throws InterruptedException, MqttException { this.reconnectionBound = reconnectionBound; this.period = period; MemoryPersistence dataStore = new MemoryPersistence(); String clientId = (this.clientID != null) ? this.clientID : newClientId(); 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$ String uriToConnect = brokerUri; mqttClient = new MqttClient(uriToConnect, clientId, dataStore); if(this.commandTimeout != IMqttConstants.UNINITIALIZED_COMMAND_TIMEOUT) { mqttClient.setTimeToWait(commandTimeout); } mqttClient.setCallback(this); if (reconnectionBound > 0) { // Bounded retry for (int i = 0; i < reconnectionBound && !shutdown; i++) { boolean success = doConnectToServer(i); if (success) break; // sleep for period before retrying Thread.sleep(period); if (isUriChanged(uriToConnect)){ // URI has changed, abort retry break; } } } else if (reconnectionBound == 0) { // no retry, so try to connect once doConnectToServer(0); } else { // Infinite retry for (int i = 0; !shutdown; i++) { boolean success = doConnectToServer(i); if (success) break; // sleep for period before retrying Thread.sleep(period); if (isUriChanged(uriToConnect)){ // URI has changed, abort retry break; } } } if (mqttClient.isConnected()) { TRACE.log(TraceLevel.INFO, "[Connect Success:]" + brokerUri); //$NON-NLS-1$ // clear when connected clearPendingBrokerUri(); } else { throw new MqttClientConnectException("Unable to connect to server: " //$NON-NLS-1$ + brokerUri); } } private void updateUserCredential() { if(this.propProvider == null) { return; } String userName = propProvider.getProperty(userPropName); String password = propProvider.getProperty(passwordPropName); conOpt.setUserName(userName); conOpt.setPassword(password.toCharArray()); TRACE.log(TraceLevel.INFO, "Using latest user credentials"); //$NON-NLS-1$ } private String newClientId() { return "streams" + System.nanoTime(); //$NON-NLS-1$ } /** * Connect to server, retrun true if successful, false otherwise * @param period * @param i * @return * @throws InterruptedException */ private boolean doConnectToServer(int i) throws InterruptedException { try { TRACE.log(TraceLevel.DEBUG, "[Connect:] " + brokerUri + " Attempt: " + i); //$NON-NLS-1$ //$NON-NLS-2$ updateUserCredential(); mqttClient.connect(conOpt); isConnected.setValue(1L); } 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$ } return mqttClient.isConnected(); } /** * 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 MqttPersistenceException * @throws MqttException * @throws InterruptedException * @throws URISyntaxException */ public void publish(String topicName, int qos, byte[] payload, boolean retain) throws MqttPersistenceException, MqttException { // Construct the message to send MqttMessage message = new MqttMessage(payload); message.setQos(qos); message.setRetained(retain); if (mqttClient != null && mqttClient.isConnected()) { mqttClient.publish(topicName, message); } } 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 = "[Subscribe:] {0} qos: {1}"; //$NON-NLS-1$ TRACE.log(TraceLevel.INFO, msg); } } mqttClient.subscribe(topics, qos); } public void unsubscribe(String[] topics) throws MqttException { if (TRACE.getLevel() == TraceLevel.INFO) { String msg = "[Unsubscribe:] " + topics; //$NON-NLS-1$ } mqttClient.unsubscribe(topics); } synchronized public void disconnect() throws MqttException { if (mqttClient != null) { if (mqttClient.isConnected()) { TRACE.log(TraceLevel.INFO, "[Disconnect:] " + brokerUri); //$NON-NLS-1$ resetMetrics(); mqttClient.disconnect(); } } } public void addCallBack(MqttCallback callback) { if(!callBackListeners.contains(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$ nConnectionLost.increment();; isConnected.setValue(0); 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; } public void setPendingBrokerUri(String pendingBrokerUri) { this.pendingBrokerUri = pendingBrokerUri; } public String getPendingBrokerUri() { return pendingBrokerUri; } public void clearPendingBrokerUri() { setPendingBrokerUri(EMPTY_STR); } public boolean isUriChanged(String uriToConnect) { if (getPendingBrokerUri().isEmpty()) return false; else if (getPendingBrokerUri().equals(uriToConnect)) return false; return true; } public void setReconnectionBound(int reconnectionBound) { this.reconnectionBound = reconnectionBound; } public void setPeriod(long period) { this.period = period; } public boolean isConnected() { if (mqttClient == null) return false; return mqttClient.isConnected(); } public void setSslProperties(Properties sslProperties) { conOpt.setSSLProperties(sslProperties); } private void resetMetrics() { this.nConnectionLost.setValue(0); this.isConnected.setValue(0); } }