com.angrygiant.mule.mqtt.MqttModule.java Source code

Java tutorial

Introduction

Here is the source code for com.angrygiant.mule.mqtt.MqttModule.java

Source

/**
 * This file was automatically generated by the Mule Development Kit
 */
package com.angrygiant.mule.mqtt;

import org.apache.commons.lang.StringUtils;
import org.apache.log4j.Logger;
import org.eclipse.paho.client.mqttv3.*;
import org.mule.api.ConnectionExceptionCode;
import org.mule.api.annotations.Module;
import org.mule.api.ConnectionException;
import org.mule.api.annotations.Configurable;
import org.mule.api.annotations.Processor;
import org.mule.api.annotations.Source;
import org.mule.api.annotations.lifecycle.Start;
import org.mule.api.annotations.lifecycle.Stop;
import org.mule.api.annotations.param.Default;
import org.mule.api.annotations.param.Optional;
import org.mule.api.annotations.param.OutboundHeaders;
import org.mule.api.annotations.param.Payload;
import org.mule.api.callback.SourceCallback;

import javax.annotation.PreDestroy;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectOutput;
import java.io.ObjectOutputStream;
import java.util.HashMap;
import java.util.Map;

/**
 * Mule MQTT Module
 * <p/>
 * Copyright (c) MuleSoft, Inc.  All rights reserved.  http://www.mulesoft.com
 * <p/>
 * <p>
 * The software in this package is published under the terms of the CPAL v1.0
 * license, a copy of which has been included with this distribution in the
 * LICENSE.md file.
 * </p>
 * <p>
 * Created with IntelliJ IDEA.
 * User: dmiller@angrygiant.com
 * Date: 9/21/12
 * Time: 9:57 AM
 * </p>
 * <p>
 * Module definition for publishing and subscribing to a given MQTT broker server.
 * </p>
 *
 * @author dmiller@angrygiant.com
 */
@Module(name = "mqtt", schemaVersion = "0.2.1")
public class MqttModule {
    private static final Logger logger = Logger.getLogger(MqttModule.class);

    public static final int MQTT_DEFAULT_PORT = 1883;
    public static final String MQTT_DEFAULT_HOST = "localhost";

    /**
     * Broker Host Name:  Host of the MQTT broker, defaults to 'localhost'
     */
    @Configurable
    @Optional
    @Default("localhost")
    private String brokerHostName = MQTT_DEFAULT_HOST;

    /**
     * Broker port number, defaults to 1883
     */
    @Configurable
    @Optional
    @Default("1883")
    private int brokerPort = MQTT_DEFAULT_PORT;

    /**
     * Client ID (required): client identifier for the broker
     */
    @Configurable
    private String clientId;

    /**
     * Clean Session - refer to Eclipse Paho documentation
     */
    @Configurable
    @Optional
    @Default("true")
    private boolean cleanSession;

    /**
     * Username to log into broker with
     */
    @Configurable
    @Optional
    private String username;

    /**
     * Password to log into broker with
     */
    @Configurable
    @Optional
    private String password;

    /**
     * Connection Timeout - refer to Eclipse Paho documentation
     */
    @Configurable
    @Optional
    @Default("30")
    private int connectionTimeout = 30;

    /**
     * Last Will and Testimate Topic - refer to Eclipse Paho documentation
     */
    @Configurable
    @Optional
    private String lwtTopicName;

    /**
     * Last Will and Testimate Message - refer to Eclipse Paho documentation
     */
    @Configurable
    @Optional
    private String lwtMessage;

    /**
     * Last Will and Testimate QOS - refer to Eclipse Paho documentation
     */
    @Configurable
    @Optional
    @Default("2")
    private int lwtQos;

    /**
     * Last Will and Testimate Retention - refer to Eclipse Paho documentation
     */
    @Configurable
    @Optional
    @Default("false")
    private boolean lwtRetained;

    /**
     * Keep Alive Interval - refer to Eclipse Paho documentation
     */
    @Configurable
    @Optional
    @Default("60")
    private int keepAliveInterval = 60;

    /**
     * File Persistence Location - directory on the machine where message persistence can be stored to disk.
     */
    @Configurable
    @Optional
    private String persistenceLocation;

    /**
     * Milliseconds of delay before subscription to a topic occurs.  Gives the client time to connect.
     */
    @Configurable
    @Optional
    @Default("500")
    private long subscriptionDelay;

    /**
     * Private variables not used by Mule directly
     */

    private MqttClient client;
    private MqttConnectOptions connectOptions = new MqttConnectOptions();

    /**
     * Initialize the connector by creating a client
     */
    @Start
    public void initialize() throws ConnectionException {
        this.client = connectClient(getClientId());

        try {
            this.client.disconnect();
        } catch (MqttException e) {
            logger.debug("ERROR: Pre-emptive disconnect created an error", e);
        }
    }

    /**
     * Convenience method that connects to a given MQTT broker.  Returns the MqttClient object
     * for later use with publish and subscribe.
     *
     * @param clientId
     * @return MqttClient
     * @throws ConnectionException
     */
    private MqttClient connectClient(String clientId) throws ConnectionException {
        logger.warn("Creating client with ID of " + clientId);
        String brokerUrl = "tcp://" + getBrokerHostName() + ":" + getBrokerPort();

        MqttDefaultFilePersistence filePersistence = null;
        if (getPersistenceLocation() != null || StringUtils.isNotBlank(getPersistenceLocation())) {
            try {
                logger.debug("Attempting to set up file persistence for client...");
                filePersistence = new MqttDefaultFilePersistence(getPersistenceLocation());
            } catch (MqttPersistenceException e) {
                logger.error("Error creating file persistence for messages...persistence will be IGNORED!!!", e);
                filePersistence = null;
            }
        }

        setupConnectOptions();

        try {
            if (filePersistence == null) {
                logger.debug("Creating client without file persistence");
                this.client = new MqttClient(brokerUrl, clientId, null);
            } else {
                logger.debug("Creating client with file persistence enabled");
                this.client = new MqttClient(brokerUrl, clientId, filePersistence);
            }
        } catch (MqttException e) {
            logger.error("Error creating client to MQTT broker:", e);
            throw new ConnectionException(ConnectionExceptionCode.UNKNOWN, null,
                    "Mule has issues with the MQTT client", e);
        }

        if (StringUtils.isNotBlank(this.getLwtTopicName())) {
            logger.debug("Setting up last will information...");
            MqttTopic lwtTopic = this.client.getTopic(this.getLwtTopicName());

            this.connectOptions.setWill(lwtTopic, this.getLwtMessage().getBytes(), this.getLwtQos(), false);
        }

        return this.client;
    }

    /**
     * Method that sets up the MqttConnectOptions class for use.  This reads the settings given via
     * the mqtt:config element.
     */
    private void setupConnectOptions() {
        connectOptions.setCleanSession(isCleanSession());
        connectOptions.setConnectionTimeout(getConnectionTimeout());
        connectOptions.setKeepAliveInterval(getKeepAliveInterval());

        if (getPassword() != null || StringUtils.isNotBlank(getPassword())) {
            connectOptions.setPassword(getPassword().toCharArray());
        }

        connectOptions.setUserName(getUsername());
    }

    /**
     * Stop the client prior to departure
     *
     * @throws MqttException
     */
    @Stop
    public void stopConnector() throws MqttException {
        if (this.client.isConnected()) {
            logger.info("Diconnecting from MQTT broker...");
            this.client.disconnect();
        }

        this.client = null;
        this.connectOptions = null;
    }

    /**
     * Publish processor - used to publish a message to a given topic on the MQTT broker.
     * <p/>
     * <p/>
     * {@sample.xml ../../../doc/mqtt-connector.xml.sample mqtt:publish}
     *
     * @param topicName       topic to publish message to
     * @param message         string message to publish (if blank/null, uses messagePayload)
     * @param qos             qos level to use when publishing message
     * @param messagePayload  injects passed payload into this object (for possible use)
     * @param outboundHeaders injected outbound headers map so we can set them prior to leaving
     * @return delivered byte[] payload of MqttMessage
     */
    @Processor
    public byte[] publish(String topicName, String message, @Optional @Default("1") int qos,
            @Payload Object messagePayload, @OutboundHeaders Map<String, Object> outboundHeaders)
            throws MqttException, IOException {
        byte[] payloadBytes;

        logger.debug("Connecting to Broker...");
        if (!this.client.isConnected()) {
            this.client.connect();
            logger.info(
                    "Connected to " + getBrokerHostName() + " on port " + getBrokerPort() + " as " + getClientId());
        } else {
            logger.debug("Already connected to Broker");
        }

        if (qos < 0 || qos > 2) {
            logger.warn("QOS is invalid, setting to default value of 2");
            qos = 2;
        }

        logger.debug("Decipher whether we're using String or Message payload");
        if (StringUtils.isNotBlank(message)) {
            logger.debug("Using the string message...");
            payloadBytes = message.getBytes();
        } else if (messagePayload instanceof byte[]) {
            logger.debug("Message payload is a byte array, using it directly...");
            payloadBytes = (byte[]) messagePayload;
        } else {
            logger.info("Converting message payload to a byte array...");
            ByteArrayOutputStream bos = new ByteArrayOutputStream();
            ObjectOutput out = new ObjectOutputStream(bos);

            try {
                out.writeObject(messagePayload);
                payloadBytes = bos.toByteArray();
            } finally {
                bos.close();
                out.close();
            }
        }

        logger.debug("Retrieving topic '" + topicName + "'");
        MqttTopic topic = this.client.getTopic(topicName);

        logger.debug("Preparing message...");
        MqttMessage mqttMessage = new MqttMessage(payloadBytes);
        mqttMessage.setQos(qos);

        logger.debug("publishing message to broker...");
        MqttDeliveryToken token = topic.publish(mqttMessage);

        logger.trace("Waiting for default timeout of 5minutes...");
        token.waitForCompletion(900000);

        if (outboundHeaders == null) {
            outboundHeaders = new HashMap<String, Object>();
        }

        outboundHeaders.put(MqttTopicListener.TOPIC_NAME, topic.getName());
        outboundHeaders.put(MqttTopicListener.MESSAGE_DUPLICATE, mqttMessage.isDuplicate());
        outboundHeaders.put(MqttTopicListener.MESSAGE_RETAIN, mqttMessage.isRetained());
        outboundHeaders.put(MqttTopicListener.MESSAGE_QOS, mqttMessage.getQos());
        outboundHeaders.put(MqttTopicListener.CLIENT_ID, this.client.getClientId());
        outboundHeaders.put(MqttTopicListener.CLIENT_URI, this.client.getServerURI());

        return mqttMessage.getPayload();
    }

    /**
     * Publish processor - used to publish a message to a given topic on the MQTT broker.
     * <p/>
     * <p/>
     * {@sample.xml ../../../doc/mqtt-connector.xml.sample mqtt:subscribe}
     *
     * @param subscriberId required subscriber id to use for subscription
     * @param topicName topic to publish message to
     * @param filter topic filter string, comma delimited if multiple (takes precedence over topic name)
     * @param qos       qos level to use when publishing message
     * @param callback  qos level to use when publishing message
     * @return
     */
    @Source
    public void subscribe(String subscriberId, @Optional String topicName, @Optional String filter,
            @Optional @Default("1") int qos, final SourceCallback callback) throws ConnectionException {
        logger.info("Creating new client for topic subscription");
        MqttClient subscriberClient = connectClient(subscriberId);

        String[] filters;
        int[] qoss;
        logger.info("Deciding whether filter or name is used...");

        if (StringUtils.isNotBlank(filter)) {
            logger.info("Building filters list");
            filters = filter.split(",");

            logger.debug("I have " + filters.length + " filters defined.  Creating matching queue of qos levels");
            qoss = new int[filters.length];

            for (int i = 0; i < filters.length; i++) {
                qoss[i] = qos;
            }
        } else {
            filters = null;
            qoss = null;
        }

        try {
            subscriberClient.disconnect();
        } catch (MqttException e) {
            logger.warn("Pre-emptive disconnect called before subscription, errors occurred: " + e);
        }

        try {
            subscriberClient.setCallback(new MqttTopicListener(subscriberClient, callback, topicName,
                    connectOptions, getSubscriptionDelay(), qos));
            subscriberClient.connect(connectOptions);

            Thread.sleep(getSubscriptionDelay());

            if (filters != null) {
                logger.debug("Subscribing to filters...");
                subscriberClient.subscribe(filters, qoss);
            } else {
                logger.debug("Subscribing to topic name...");
                subscriberClient.subscribe(topicName, qos);
            }
        } catch (MqttException e) {
            logger.error("MQTT Exceptions occurred subscribing", e);
            throw new ConnectionException(ConnectionExceptionCode.UNKNOWN, null, "Subscription Error", e);
        } catch (InterruptedException ie) {
            logger.error("Interrupt exception occurred sleeping before subscribing...");
            throw new ConnectionException(ConnectionExceptionCode.UNKNOWN, null, "Interrupt Error", ie);
        }

        logger.info("Done subscribing to topic " + topicName);
    }

    //Getters and Setters

    public String getBrokerHostName() {
        return brokerHostName;
    }

    public void setBrokerHostName(String brokerHostName) {
        this.brokerHostName = brokerHostName;
    }

    public int getBrokerPort() {
        return brokerPort;
    }

    public void setBrokerPort(int brokerPort) {
        this.brokerPort = brokerPort;
    }

    public String getClientId() {
        return clientId;
    }

    public void setClientId(String clientId) {
        this.clientId = clientId;
    }

    public boolean isCleanSession() {
        return cleanSession;
    }

    public void setCleanSession(boolean cleanSession) {
        this.cleanSession = cleanSession;
    }

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    public int getConnectionTimeout() {
        return connectionTimeout;
    }

    public void setConnectionTimeout(int connectionTimeout) {
        this.connectionTimeout = connectionTimeout;
    }

    public String getLwtTopicName() {
        return lwtTopicName;
    }

    public void setLwtTopicName(String lwtTopicName) {
        this.lwtTopicName = lwtTopicName;
    }

    public String getLwtMessage() {
        return lwtMessage;
    }

    public void setLwtMessage(String lwtMessage) {
        this.lwtMessage = lwtMessage;
    }

    public int getKeepAliveInterval() {
        return keepAliveInterval;
    }

    public void setKeepAliveInterval(int keepAliveInterval) {
        this.keepAliveInterval = keepAliveInterval;
    }

    public String getPersistenceLocation() {
        return persistenceLocation;
    }

    public void setPersistenceLocation(String persistenceLocation) {
        this.persistenceLocation = persistenceLocation;
    }

    public int getLwtQos() {
        return lwtQos;
    }

    public void setLwtQos(int lwtQos) {
        this.lwtQos = lwtQos;
    }

    public boolean isLwtRetained() {
        return lwtRetained;
    }

    public void setLwtRetained(boolean lwtRetained) {
        this.lwtRetained = lwtRetained;
    }

    public long getSubscriptionDelay() {
        return subscriptionDelay;
    }

    public void setSubscriptionDelay(long subscriptionDelay) {
        this.subscriptionDelay = subscriptionDelay;
    }
}