/*
* 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.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.synapse.inbound.InboundProcessorParams;
import org.eclipse.paho.client.mqttv3.MqttAsyncClient;
import org.eclipse.paho.client.mqttv3.MqttConnectOptions;
import org.eclipse.paho.client.mqttv3.MqttException;
import org.wso2.carbon.context.PrivilegedCarbonContext;
import org.wso2.carbon.inbound.endpoint.common.InboundOneTimeTriggerRequestProcessor;
import org.wso2.carbon.inbound.endpoint.protocol.PollingConstants;
import java.util.Properties;
import javax.net.ssl.SSLSocketFactory;
/**
* This is the listener which directly interacts with the external MQTT server. Every MQTT
* inbound listener is bound to Server Port, Server Host, Client ID.
*/
public class MqttListener extends InboundOneTimeTriggerRequestProcessor {
private static final String ENDPOINT_POSTFIX = "MQTT" + COMMON_ENDPOINT_POSTFIX;
private static final Log log = LogFactory.getLog(MqttListener.class);
private String injectingSeq;
private String onErrorSeq;
private Properties mqttProperties;
private String contentType;
private boolean sequential;
private MqttConnectionFactory confac;
private MqttAsyncClient mqttAsyncClient;
private MqttAsyncCallback mqttAsyncCallback;
private MqttConnectOptions connectOptions;
private MqttConnectionConsumer connectionConsumer;
private MqttInjectHandler injectHandler;
protected String userName;
protected String password;
protected boolean cleanSession;
private InboundProcessorParams params;
private SSLSocketFactory socketFactory;
/**
* constructor for the MQTT inbound endpoint listener *
*
* @param params
*/
public MqttListener(InboundProcessorParams params) {
this.name = params.getName();
this.injectingSeq = params.getInjectingSeq();
this.onErrorSeq = params.getOnErrorSeq();
this.synapseEnvironment = params.getSynapseEnvironment();
this.mqttProperties = params.getProperties();
this.params = params;
//assign default value if sequential mode parameter is not present
this.sequential = true;
if (mqttProperties.getProperty(PollingConstants.INBOUND_ENDPOINT_SEQUENTIAL) != null) {
this.sequential =
Boolean.parseBoolean(mqttProperties.getProperty
(PollingConstants.INBOUND_ENDPOINT_SEQUENTIAL));
}
//assign default value if coordination mode parameter is not present
this.coordination = true;
if (mqttProperties.getProperty(PollingConstants.INBOUND_COORDINATION) != null) {
this.coordination =
Boolean.parseBoolean(mqttProperties.getProperty
(PollingConstants.INBOUND_COORDINATION));
}
this.confac = new MqttConnectionFactory(mqttProperties);
this.contentType = confac.getContent();
this.injectHandler =
new MqttInjectHandler(injectingSeq, onErrorSeq, sequential,
synapseEnvironment, contentType);
this.synapseEnvironment = params.getSynapseEnvironment();
this.socketFactory = confac.getSSLSocketFactory();
//mqtt connection options
if (mqttProperties.getProperty(MqttConstants.MQTT_USERNAME) != null) {
this.userName = mqttProperties.getProperty(MqttConstants.MQTT_USERNAME);
}
if (mqttProperties.getProperty(MqttConstants.MQTT_PASSWORD) != null) {
this.password = mqttProperties.getProperty(MqttConstants.MQTT_PASSWORD);
}
if (mqttProperties.getProperty(MqttConstants.MQTT_SESSION_CLEAN) != null) {
this.cleanSession =
Boolean.parseBoolean(mqttProperties.getProperty(MqttConstants.MQTT_SESSION_CLEAN));
}
}
@Override
public void destroy() {
log.info("Mqtt Inbound endpoint: " + name + " Started destroying context.");
MqttClientManager clientManager = MqttClientManager.getInstance();
String inboundIdentifier = clientManager.buildIdentifier(mqttAsyncClient.getClientId(),
confac.getServerHost(), confac.getServerPort());
//we should ignore the case of manually loading of tenant
//we maintain a flag for cases where we load the tenant manually
if (!clientManager.isInboundTenantLoadingFlagSet(inboundIdentifier)) {
//release the thread from suspension
//this will release thread suspended thread for completion
connectionConsumer.shutdown();
mqttAsyncCallback.shutdown();
confac.shutdown(mqttAsyncClient.isConnected());
try {
if (mqttAsyncClient.isConnected()) {
mqttAsyncClient.unsubscribe(confac.getTopic());
mqttAsyncClient.disconnect();
}
mqttAsyncClient.close();
PrivilegedCarbonContext carbonContext =
PrivilegedCarbonContext.getThreadLocalCarbonContext();
int tenantId = carbonContext.getTenantId();
String nameIdentifier = clientManager
.buildNameIdentifier(name, String.valueOf(tenantId));
//here we unregister it because this is not a case of tenant loading
MqttClientManager.getInstance()
.unregisterMqttClient(inboundIdentifier, nameIdentifier);
log.info("Disconnected from the remote MQTT server.");
} catch (MqttException e) {
log.error("Error while disconnecting from the remote server.");
}
}
super.destroy();
}
@Override
public void init() {
log.info("MQTT inbound endpoint " + name + " initializing ...");
initAsyncClient();
start();
}
public void initAsyncClient() {
mqttAsyncClient = confac.getMqttAsyncClient(this.name);
MqttClientManager clientManager = MqttClientManager.getInstance();
String inboundIdentifier = clientManager.buildIdentifier(mqttAsyncClient.getClientId(),
confac.getServerHost(), confac.getServerPort());
if (!clientManager.hasMqttCallback(inboundIdentifier)) {
//registering callback for the first time
connectOptions = new MqttConnectOptions();
connectOptions.setCleanSession(cleanSession);
if (userName != null && password != null) {
connectOptions.setUserName(userName);
connectOptions.setPassword(password.toCharArray());
}
if (socketFactory != null) {
connectOptions.setSocketFactory(socketFactory);
}
mqttAsyncCallback = new MqttAsyncCallback(mqttAsyncClient, injectHandler,
confac, connectOptions, mqttProperties);
mqttAsyncCallback.setName(params.getName());
connectionConsumer = new MqttConnectionConsumer(connectOptions, mqttAsyncClient,
confac, mqttProperties, name);
mqttAsyncCallback.setMqttConnectionConsumer(connectionConsumer);
mqttAsyncClient.setCallback(mqttAsyncCallback);
//here we register the callback handler
clientManager.registerMqttCallback(inboundIdentifier, mqttAsyncCallback);
} else {
//has previously registered callback we just update the reference
//in other words has previous un-destroyed callback
//this is a manually tenant loading case
//should clear the previously set tenant loading flags for the inbound identifier
clientManager.unRegisterInboundTenantLoadingFlag(inboundIdentifier);
mqttAsyncCallback = clientManager.getMqttCallback(inboundIdentifier);
mqttAsyncCallback.setName(params.getName());
connectOptions = mqttAsyncCallback.getMqttConnectionOptions();
connectionConsumer = mqttAsyncCallback.getMqttConnectionConsumer();
//but we need to update injectHandler due to recreation of synapse environment
mqttAsyncCallback.updateInjectHandler(injectHandler);
}
}
public void start() {
MqttTask mqttTask = new MqttTask(connectionConsumer);
mqttTask.setCallback(mqttAsyncCallback);
start(mqttTask, ENDPOINT_POSTFIX);
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}