/* * 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.inbound.endpoint.protocol.mqtt; import org.apache.commons.lang.StringUtils; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.apache.synapse.SynapseException; import org.eclipse.paho.client.mqttv3.MqttAsyncClient; import org.eclipse.paho.client.mqttv3.MqttException; import org.eclipse.paho.client.mqttv3.MqttPersistenceException; import org.eclipse.paho.client.mqttv3.persist.MqttDefaultFilePersistence; import org.wso2.carbon.context.PrivilegedCarbonContext; import javax.net.ssl.KeyManagerFactory; import javax.net.ssl.SSLContext; import javax.net.ssl.SSLSocketFactory; import javax.net.ssl.TrustManagerFactory; import java.io.FileInputStream; import java.security.KeyStore; import java.util.Hashtable; import java.util.Properties; /** * MQTT factory, return instances of asynchronous clients */ public class MqttConnectionFactory { private static final Log log = LogFactory.getLog(MqttConnectionFactory.class); private String factoryName; private Hashtable<String, String> parameters = new Hashtable<String, String>(); private MqttDefaultFilePersistence dataStore; private SSLSocketFactory socketFactory; private static final int PORT_MIN_BOUND = 0; private static final int PORT_MAX_BOUND = 65535; public MqttConnectionFactory(Properties passedInParameter) { this.factoryName = passedInParameter.getProperty(MqttConstants.PARAM_MQTT_CONFAC); try { if (passedInParameter.getProperty(MqttConstants.MQTT_SERVER_HOST_NAME) != null) { parameters.put(MqttConstants.MQTT_SERVER_HOST_NAME, passedInParameter.getProperty(MqttConstants.MQTT_SERVER_HOST_NAME)); } else { String msg = "MQTT inbound listener Host Name cannot be empty"; log.error(msg); throw new SynapseException(msg); } if (passedInParameter.getProperty(MqttConstants.MQTT_TOPIC_NAME) != null) { parameters.put(MqttConstants.MQTT_TOPIC_NAME, passedInParameter.getProperty(MqttConstants.MQTT_TOPIC_NAME)); } else { String msg = "MQTT inbound listener Subscription Topic Name cannot be empty"; log.error(msg); throw new SynapseException(msg); } if (passedInParameter.getProperty(MqttConstants.MQTT_SERVER_PORT) != null) { validatePortField(passedInParameter.getProperty(MqttConstants.MQTT_SERVER_PORT)); parameters.put(MqttConstants.MQTT_SERVER_PORT, passedInParameter.getProperty(MqttConstants.MQTT_SERVER_PORT)); } else { String msg = "MQTT inbound listener Port Number cannot be empty"; log.error(msg); throw new SynapseException(msg); } if (passedInParameter.getProperty(MqttConstants.CONTENT_TYPE) != null) { parameters.put(MqttConstants.CONTENT_TYPE, passedInParameter.getProperty(MqttConstants.CONTENT_TYPE)); } else { log.warn("Default value is used for the parameter : " + MqttConstants.CONTENT_TYPE); } if (passedInParameter.getProperty(MqttConstants.MQTT_QOS) == null) { parameters.put(MqttConstants.MQTT_QOS, "1"); log.warn("Default value is used for the parameter : " + MqttConstants.MQTT_QOS); } if (passedInParameter.getProperty(MqttConstants.MQTT_QOS) != null) { int qos = Integer.parseInt(passedInParameter.getProperty(MqttConstants.MQTT_QOS)); if (qos == 2 || qos == 1 || qos == 0) { parameters.put(MqttConstants.MQTT_QOS, passedInParameter.getProperty(MqttConstants.MQTT_QOS)); } else { parameters.put(MqttConstants.MQTT_QOS, "1"); log.warn("Default value is used for the parameter : " + MqttConstants.MQTT_QOS); } } if (passedInParameter.getProperty(MqttConstants.MQTT_TEMP_STORE) != null) { parameters.put(MqttConstants.MQTT_TEMP_STORE, passedInParameter.getProperty(MqttConstants.MQTT_TEMP_STORE)); } else { log.warn("Default value is used for the parameter : " + MqttConstants.MQTT_TEMP_STORE); } if (passedInParameter.getProperty(MqttConstants.MQTT_SESSION_CLEAN) != null) { parameters.put(MqttConstants.MQTT_SESSION_CLEAN, passedInParameter.getProperty(MqttConstants.MQTT_SESSION_CLEAN)); } else { log.warn("Default value is used for the parameter : " + MqttConstants.MQTT_SESSION_CLEAN); } if (passedInParameter.getProperty(MqttConstants.MQTT_SSL_ENABLE) != null) { parameters.put(MqttConstants.MQTT_SSL_ENABLE, passedInParameter.getProperty(MqttConstants.MQTT_SSL_ENABLE)); if (parameters.get(MqttConstants.MQTT_SSL_ENABLE).equalsIgnoreCase("true")) { String keyStoreLocation = passedInParameter .getProperty(MqttConstants.MQTT_SSL_KEYSTORE_LOCATION); String keyStoreType = passedInParameter .getProperty(MqttConstants.MQTT_SSL_KEYSTORE_TYPE); String keyStorePassword = passedInParameter .getProperty(MqttConstants.MQTT_SSL_KEYSTORE_PASSWORD); String trustStoreLocation = passedInParameter .getProperty(MqttConstants.MQTT_SSL_TRUSTSTORE_LOCATION); String trustStoreType = passedInParameter .getProperty(MqttConstants.MQTT_SSL_TRUSTSTORE_TYPE); String trustStorePassword = passedInParameter .getProperty(MqttConstants.MQTT_SSL_TRUSTSTORE_PASSWORD); String sslVersion = passedInParameter .getProperty(MqttConstants.MQTT_SSL_VERSION); if (StringUtils.isEmpty(keyStoreLocation) || StringUtils.isEmpty(keyStoreType) || StringUtils.isEmpty(keyStorePassword) || StringUtils.isEmpty(trustStoreLocation) || StringUtils.isEmpty(trustStoreType) || StringUtils.isEmpty(trustStorePassword) || StringUtils.isEmpty(sslVersion)) { String msg = "Configuration for Truststore and Keystore is insufficient to enable SSL"; log.error(msg); throw new SynapseException(msg); } else { socketFactory = getSocketFactory(keyStoreLocation, keyStoreType, keyStorePassword, trustStoreLocation, trustStoreType, trustStorePassword, sslVersion); } } } else { log.warn("Default value is used for the parameter : " + MqttConstants.MQTT_SSL_ENABLE); } if (passedInParameter.getProperty(MqttConstants.MQTT_CLIENT_ID) != null) { parameters.put(MqttConstants.MQTT_CLIENT_ID, passedInParameter.getProperty(MqttConstants.MQTT_CLIENT_ID)); } else { log.warn("Default value is used for the parameter : " + MqttConstants.MQTT_CLIENT_ID); } if (passedInParameter.getProperty(MqttConstants.MQTT_RECONNECTION_INTERVAL) != null) { parameters.put(MqttConstants.MQTT_RECONNECTION_INTERVAL, passedInParameter.getProperty(MqttConstants.MQTT_RECONNECTION_INTERVAL)); } else { log.warn("Default value is used for the parameter : " + MqttConstants.MQTT_RECONNECTION_INTERVAL); } } catch (Exception ex) { log.error("MQTT connection factory : " + factoryName + " failed to initialize " + "the MQTT Inbound configuration properties", ex); //this will prevent the inbound from deployment if anything goes bad and exception //thrown at this point will be thrown to the higher layer throw new SynapseException(ex.getMessage()); } } public String getName() { return factoryName; } public MqttAsyncClient getMqttAsyncClient(String name) { return createMqttAsyncClient(name); } public String getTopic() { return parameters.get(MqttConstants.MQTT_TOPIC_NAME); } public String getContent() { return parameters.get(MqttConstants.CONTENT_TYPE); } public String getServerHost() { return parameters.get(MqttConstants.MQTT_SERVER_HOST_NAME); } public String getServerPort() { return parameters.get(MqttConstants.MQTT_SERVER_PORT); } public SSLSocketFactory getSSLSocketFactory(){ return socketFactory; } public int getReconnectionInterval() { if (parameters.get(MqttConstants.MQTT_RECONNECTION_INTERVAL) != null) { return Integer.parseInt(parameters.get(MqttConstants.MQTT_RECONNECTION_INTERVAL)); } else { return -1; } } private MqttAsyncClient createMqttAsyncClient(String name) { MqttClientManager clientManager = MqttClientManager.getInstance(); String uniqueClientId; String inboundIdentifier; if (parameters.get(MqttConstants.MQTT_CLIENT_ID) != null) { uniqueClientId = parameters.get(MqttConstants.MQTT_CLIENT_ID); } else { uniqueClientId = MqttAsyncClient.generateClientId(); } PrivilegedCarbonContext carbonContext = PrivilegedCarbonContext.getThreadLocalCarbonContext(); int tenantId = carbonContext.getTenantId(); name = clientManager.buildNameIdentifier(name, String.valueOf(tenantId)); if (clientManager.hasInboundEndpoint(name)) { inboundIdentifier = clientManager.getInboundEndpointIdentifier(name); } else { inboundIdentifier = clientManager. buildIdentifier(uniqueClientId, getServerHost(), getServerPort()); } if (clientManager.hasMqttClient(inboundIdentifier)) { //update data store reference if (clientManager.hasClientDataStore(inboundIdentifier)) { dataStore = clientManager.getMqttClientDataStore(inboundIdentifier); } return clientManager.getMqttClient(inboundIdentifier); } String sslEnable = parameters.get(MqttConstants.MQTT_SSL_ENABLE); // This sample stores in a temporary directory... where messages // temporarily // stored until the message has been delivered to the server. String tmpDir = parameters.get(MqttConstants.MQTT_TEMP_STORE); dataStore = null; int qos = Integer.parseInt(parameters.get(MqttConstants.MQTT_QOS.toString())); if (qos == 2 || qos == 1) { if (tmpDir != null) { dataStore = new MqttDefaultFilePersistence(tmpDir); } else { tmpDir = System.getProperty("java.io.tmpdir"); dataStore = new MqttDefaultFilePersistence(tmpDir); } } else { dataStore = null; } String mqttEndpointURL = "tcp://" + parameters.get(MqttConstants.MQTT_SERVER_HOST_NAME) + ":" + parameters.get(MqttConstants.MQTT_SERVER_PORT); // If SSL is enabled in the config, Use SSL transport if (sslEnable != null && sslEnable.equalsIgnoreCase("true")) { mqttEndpointURL = "ssl://" + parameters.get(MqttConstants.MQTT_SERVER_HOST_NAME) + ":" + parameters.get(MqttConstants.MQTT_SERVER_PORT); } MqttAsyncClient mqttClient = null; try { mqttClient = new MqttAsyncClient(mqttEndpointURL, uniqueClientId, dataStore); log.info("Successfully created MQTT client"); } catch (MqttException ex) { log.error("Error while creating the MQTT asynchronous client", ex); } //here we register the created client in Mqtt clientManager clientManager.registerInboundEndpoint(name, inboundIdentifier); clientManager.registerMqttClient(inboundIdentifier, mqttClient); //register dataStore for Client if (dataStore != null) { clientManager.registerClientDataStore(inboundIdentifier, dataStore); } return mqttClient; } protected void validatePortField(String port) { try { int portInteger = Integer.parseInt(port); if ((PORT_MIN_BOUND < portInteger) && (portInteger < PORT_MAX_BOUND)) { //this is a valid port integer so just return return; } else { //in this case port number is not bounded to min and max, throwing synapse //exception will prevent the inbound from deployment String msg = "Server Port number should be bounded to min integer value: " + PORT_MIN_BOUND + " and max integer value: " + PORT_MAX_BOUND; log.error(msg); throw new SynapseException(msg); } } catch (NumberFormatException ex) { //in this case port string contain any special characters, throwing synapse //exception will prevent the inbound from deployment String msg = "Server Port number should not contain any special characters"; log.error(msg); throw new SynapseException(msg); } } public void shutdown(boolean isClientConnected) { //need to clear the resources if and only if client holds the lock for the resource //that is client has made a successful connection to the server //this will clear the persistence resources and releases the the lock bound to that resource if (dataStore != null && isClientConnected) { try { dataStore.clear(); dataStore.close(); } catch (MqttPersistenceException ex) { log.error("Error while releasing the resources for data store", ex); } } } protected SSLSocketFactory getSocketFactory(String keyStoreLocation, String keyStoreType, String keyStorePassword, String trustStoreLocation, String trustStoreType, String trustStorePassword, String sslVersion) throws Exception { char[] keyPassphrase = keyStorePassword.toCharArray(); KeyStore keyStore = KeyStore.getInstance(keyStoreType); keyStore.load(new FileInputStream(keyStoreLocation), keyPassphrase); KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm()); keyManagerFactory.init(keyStore, keyPassphrase); char[] trustPassphrase = trustStorePassword.toCharArray(); KeyStore trustStore = KeyStore.getInstance(trustStoreType); trustStore.load(new FileInputStream(trustStoreLocation), trustPassphrase); TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm()); trustManagerFactory.init(trustStore); SSLContext sslContext = SSLContext.getInstance(sslVersion); sslContext.init(keyManagerFactory.getKeyManagers(), trustManagerFactory.getTrustManagers(), null); return sslContext.getSocketFactory(); } }