org.jboss.fuse.mqtt.interop.MqttTestClient.java Source code

Java tutorial

Introduction

Here is the source code for org.jboss.fuse.mqtt.interop.MqttTestClient.java

Source

package org.jboss.fuse.mqtt.interop;
/**
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF 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.
 */

import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;

import org.apache.commons.cli.CommandLine;
import org.apache.commons.cli.CommandLineParser;
import org.apache.commons.cli.GnuParser;
import org.apache.commons.cli.HelpFormatter;
import org.apache.commons.cli.Options;
import org.apache.commons.cli.ParseException;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.fusesource.mqtt.client.BlockingConnection;
import org.fusesource.mqtt.client.MQTT;
import org.fusesource.mqtt.client.Message;
import org.fusesource.mqtt.client.QoS;
import org.fusesource.mqtt.client.Topic;
import org.fusesource.mqtt.client.Tracer;
import org.fusesource.mqtt.codec.MQTTFrame;

/**
 * @author dbokde
 */
public class MqttTestClient {

    public static final Log LOG = LogFactory.getLog(MqttTestClient.class);

    private static final String DEFAULT_HOST = "localhost";
    private static final String DEFAULT_PORT = "1883";
    private static final String MYCLIENTID = "myclientid";
    private static final String MYCLIENTID2 = "myclientid2";

    public static final String[] CLIENT_IDS = { MYCLIENTID, MYCLIENTID2 };
    public static final String[] TOPICS = { "TopicA", "TopicA/B", "Topic/C", "TopicA/C", "/TopicA" };
    public static final String[] WILD_TOPICS = { "TopicA/+", "+/C", "#", "/#", "/+", "+/+", "TopicA/#" };

    public static final String NO_SUBSCRIBE_TOPIC = "nosubscribe";

    private static final byte[] EMPTY_MESSAGE = "".getBytes();
    private static final byte[] QOS0_MESSAGE = "qos 0".getBytes();
    private static final byte[] QOS1_MESSAGE = "qos 1".getBytes();
    private static final byte[] QOS2_MESSAGE = "qos 2".getBytes();
    private static final int RECEIVE_TIMEOUT = 3000;

    private MQTT mqtt;

    public static void main(String[] args) {
        Options options = new Options();
        options.addOption("help", false, "print this message").addOption("trace", false, "enable packet tracing")
                .addOption("host", true, "MQTT broker host, default localhost")
                .addOption("port", true, "MQTT broker port, default 1883").addOption("user", true, "user name")
                .addOption("password", true, "user password")
                .addOption("zero_length_clientid", false, "run zero length client id test")
                .addOption("dollar_topics", false, "run zero length client id test")
                .addOption("subscribe_failure", false, "run subscribe failure test")
                .addOption("iterations", true, "test suite iterations");
        CommandLineParser parser = new GnuParser();
        final CommandLine commandLine;
        try {
            commandLine = parser.parse(options, args);
            if (commandLine.hasOption("help")) {
                HelpFormatter formatter = new HelpFormatter();
                formatter.printHelp("java " + MqttTestClient.class.getName(), options);
                return;
            }
            new MqttTestClient().run(commandLine);
        } catch (ParseException e) {
            LOG.error("Error parsing command line: " + e.getMessage());
        }
    }

    private void run(CommandLine commandLine) {
        final String host = commandLine.getOptionValue("host", DEFAULT_HOST);
        final int port = Integer.valueOf(commandLine.getOptionValue("port", DEFAULT_PORT));
        LOG.info("Test broker at " + host + ":" + port);
        final String user = commandLine.getOptionValue("user");
        final String password = commandLine.getOptionValue("password");

        // connect
        mqtt = new MQTT();
        mqtt.setVersion("3.1.1");
        if (commandLine.hasOption("trace")) {
            mqtt.setTracer(new Tracer() {
                @Override
                public void onSend(MQTTFrame frame) {
                    LOG.info("Client sending " + frame);
                }

                @Override
                public void onReceive(MQTTFrame frame) {
                    LOG.info("Client received " + frame);
                }
            });
        }
        boolean succeded = true;
        try {
            mqtt.setHost(host, port);
            if (user != null && password != null) {
                mqtt.setUserName(user);
                mqtt.setPassword(password);
            }

            // run the various tests
            MqttTest tests[] = new MqttTest[] { new Basic(), new RetainedMessage(), new OfflineMessageQueueing(),
                    new WillMessage(), new OverlappingSubscriptions(), new KeepAlive(), new RedeliveryOnReconnect(),
                    commandLine.hasOption("zero_length_clientid") ? new ZeroLengthClientId() : null,
                    commandLine.hasOption("dollar_topics") ? new DollarTopics() : null,
                    commandLine.hasOption("subscribe_failure") ? new SubscribeFailure() : null };

            int iterations = Integer.parseInt(commandLine.getOptionValue("iterations", "1"));
            LOG.info("Running test suite " + iterations + " times");
            for (int i = 0; i < iterations; i++) {
                cleanup();

                if (iterations != 1) {
                    LOG.info("Test suite iteration " + i);
                }
                for (MqttTest test : tests) {
                    if (test != null) {
                        LOG.info("Running " + getTestName(test.getClass().getName()) + " test...");
                        succeded &= test.run();
                    }
                }
            }
        } catch (Exception e) {
            LOG.error("Error in test client " + e.getMessage(), e);
            succeded = false;
        } finally {
            if (succeded) {
                LOG.info("Test suite succeeded");
            } else {
                LOG.error("Test suite failed!");
            }
        }
    }

    private void cleanup() throws Exception {
        // clean sessions
        for (String clientId : CLIENT_IDS) {
            MQTT mqtt1 = new MQTT(mqtt);

            mqtt1.setClientId(clientId);
            final BlockingConnection connection = mqtt1.blockingConnection();
            try {
                connection.connect();
                // clean all topics
                Topic topics[] = new Topic[TOPICS.length];
                for (int i = 0; i < topics.length; i++) {
                    topics[i] = new Topic(TOPICS[i], QoS.EXACTLY_ONCE);
                }
                connection.subscribe(topics);
                receiveMessages(connection);
                connection.unsubscribe(TOPICS);
            } finally {
                safeDisconnect(connection);
            }
        }

        // clean retained messages
        MQTT mqtt1 = new MQTT(mqtt);
        mqtt1.setClientId("clean retained");
        final BlockingConnection connection = mqtt1.blockingConnection();
        try {
            connection.connect();
            connection.subscribe(new Topic[] { new Topic("#", QoS.AT_MOST_ONCE) });
            List<Message> msgs = new ArrayList<Message>();
            Message msg;
            while ((msg = connection.receive(1000, TimeUnit.MILLISECONDS)) != null) {
                msgs.add(msg);
            }
            for (Message m : msgs) {
                connection.publish(m.getTopic(), "".getBytes(), QoS.AT_MOST_ONCE, true);
            }
        } finally {
            safeDisconnect(connection);
        }
    }

    private class Basic implements MqttTest {
        @Override
        public boolean run() {
            boolean succeeded = true;

            final BlockingConnection connection = getBlockingConnection(MYCLIENTID, true);
            try {
                connection.connect();
                connection.subscribe(new Topic[] { new Topic(TOPICS[0], QoS.EXACTLY_ONCE) });

                connection.publish(TOPICS[0], QOS0_MESSAGE, QoS.AT_MOST_ONCE, false);
                connection.publish(TOPICS[0], QOS1_MESSAGE, QoS.AT_LEAST_ONCE, false);
                connection.publish(TOPICS[0], QOS2_MESSAGE, QoS.EXACTLY_ONCE, false);

                succeeded = receiveExpectedMessages(connection, 3);
                connection.disconnect();

                /* ignored, since its ends up testing client code anyway
                                connection.connect();
                                try {
                connection.connect();
                LOG.error("Duplicate connect must be rejected");
                succeeded = false;
                                } catch (Exception expected) {
                                }
                                connection.disconnect();
                */
            } catch (Exception e) {
                LOG.error(e.getMessage(), e);
                succeeded = false;
            } finally {
                logResult(connection, succeeded);
            }
            return succeeded;
        }
    }

    private class RetainedMessage implements MqttTest {
        @Override
        public boolean run() {
            boolean succeeded = true;

            BlockingConnection connection = getBlockingConnection(MYCLIENTID, true);
            try {
                connection.connect();

                connection.publish(TOPICS[1], QOS0_MESSAGE, QoS.AT_MOST_ONCE, true);
                connection.publish(TOPICS[2], QOS1_MESSAGE, QoS.AT_LEAST_ONCE, true);
                connection.publish(TOPICS[3], QOS2_MESSAGE, QoS.EXACTLY_ONCE, true);

                connection.subscribe(new Topic[] { new Topic(WILD_TOPICS[5], QoS.EXACTLY_ONCE) });
                succeeded = receiveExpectedMessages(connection, 3);
                connection.disconnect();

                // clear retained messages
                connection = getBlockingConnection(MYCLIENTID, true);
                connection.connect();
                connection.publish(TOPICS[1], EMPTY_MESSAGE, QoS.AT_MOST_ONCE, true);
                connection.publish(TOPICS[2], EMPTY_MESSAGE, QoS.AT_LEAST_ONCE, true);
                connection.publish(TOPICS[3], EMPTY_MESSAGE, QoS.EXACTLY_ONCE, true);

                connection.subscribe(new Topic[] { new Topic(WILD_TOPICS[5], QoS.EXACTLY_ONCE) });
                succeeded = receiveExpectedMessages(connection, 0);
                connection.disconnect();

            } catch (Exception e) {
                LOG.error(e.getMessage(), e);
                succeeded = false;
            } finally {
                logResult(connection, succeeded);
            }
            return succeeded;
        }
    }

    private class OfflineMessageQueueing implements MqttTest {
        @Override
        public boolean run() {
            boolean succeeded = true;

            BlockingConnection connection = getBlockingConnection(MYCLIENTID, false);
            BlockingConnection connection2 = getBlockingConnection(MYCLIENTID2, true);
            try {
                connection.connect();
                connection.subscribe(new Topic[] { new Topic(WILD_TOPICS[5], QoS.EXACTLY_ONCE) });
                connection.disconnect();

                connection2.connect();
                connection2.publish(TOPICS[1], QOS0_MESSAGE, QoS.AT_MOST_ONCE, false);
                connection2.publish(TOPICS[2], QOS1_MESSAGE, QoS.AT_LEAST_ONCE, false);
                connection2.publish(TOPICS[3], QOS2_MESSAGE, QoS.EXACTLY_ONCE, false);
                connection2.disconnect();

                connection = getBlockingConnection(MYCLIENTID, false);
                connection.connect();
                final int count = receiveMessages(connection);
                if (count < 2 || count > 3) {
                    LOG.error("Excepted 2 or 3 messages, received " + count);
                    succeeded = false;
                } else {
                    LOG.info("This server " + (count == 3 ? "is" : "is not")
                            + " queueing Qos0 messages for offline clients");
                }
            } catch (Exception e) {
                LOG.error(e.getMessage(), e);
                succeeded = false;
            } finally {
                safeDisconnect(connection2);
                logResult(connection, succeeded);
            }
            return succeeded;
        }
    }

    private class WillMessage implements MqttTest {
        @Override
        public boolean run() {
            boolean succeeded = true;
            MQTT mqtt1 = new MQTT(mqtt);
            mqtt1.setClientId(MYCLIENTID);
            mqtt1.setWillTopic(TOPICS[2]);
            mqtt1.setWillMessage("client not disconnected");
            mqtt1.setWillQos(QoS.EXACTLY_ONCE);
            mqtt1.setKeepAlive((short) 2);

            final BlockingConnection connection = mqtt1.blockingConnection();
            final BlockingConnection connection1 = getBlockingConnection(MYCLIENTID2, false);
            try {
                connection.connect();

                connection1.connect();
                connection1.subscribe(new Topic[] { new Topic(TOPICS[2], QoS.EXACTLY_ONCE) });

                connection.kill();
                Thread.sleep(5000);
                succeeded = receiveExpectedMessages(connection1, 1);

            } catch (Exception e) {
                LOG.error(e.getMessage(), e);
                succeeded = false;
            } finally {
                safeDisconnect(connection1);
                logResult(connection, succeeded);
            }

            return succeeded;
        }
    }

    private class OverlappingSubscriptions implements MqttTest {
        @Override
        public boolean run() {
            boolean succeeded = true;
            BlockingConnection connection = getBlockingConnection(MYCLIENTID, true);
            try {
                connection.connect();
                connection.subscribe(new Topic[] { new Topic(WILD_TOPICS[6], QoS.EXACTLY_ONCE),
                        new Topic(WILD_TOPICS[0], QoS.AT_LEAST_ONCE) });

                connection.publish(TOPICS[3], QOS2_MESSAGE, QoS.EXACTLY_ONCE, false);
                final int count = receiveMessages(connection);
                if (count < 1 || count > 2) {
                    LOG.error("Excepted 1 or 2 messages, received " + count);
                    succeeded = false;
                } else {
                    LOG.info("This server is publishing one message " + (count == 1 ? " for all" : "per each")
                            + " matching overlapping subscriptions");
                }
                connection.disconnect();
            } catch (Exception e) {
                LOG.error(e.getMessage(), e);
                succeeded = false;
            } finally {
                logResult(connection, succeeded);
            }
            return succeeded;
        }
    }

    private class KeepAlive implements MqttTest {
        @Override
        public boolean run() {
            boolean succeeded = true;
            MQTT mqtt1 = new MQTT(mqtt);
            mqtt1.setClientId(MYCLIENTID);
            mqtt1.setWillTopic(TOPICS[4]);
            mqtt1.setWillMessage("keepalive expiry");
            mqtt1.setWillQos(QoS.EXACTLY_ONCE);
            mqtt1.setKeepAlive((short) 5);
            final BlockingConnection connection = mqtt1.blockingConnection();

            final BlockingConnection connection1 = getBlockingConnection(MYCLIENTID2, true);
            try {
                connection.connect();
                connection.suspend();

                connection1.connect();
                connection1.subscribe(new Topic[] { new Topic(TOPICS[4], QoS.EXACTLY_ONCE) });

                Thread.sleep(15 * 1000);
                succeeded = receiveExpectedMessages(connection1, 1);
            } catch (Exception e) {
                LOG.error(e.getMessage(), e);
                succeeded = false;
            } finally {
                safeDisconnect(connection1);
                logResult(connection, succeeded);
            }
            return succeeded;
        }
    }

    private class RedeliveryOnReconnect implements MqttTest {
        @Override
        public boolean run() {
            boolean succeeded = true;
            BlockingConnection connection = getBlockingConnection(MYCLIENTID, false);
            try {
                connection.connect();
                connection.subscribe(new Topic[] { new Topic(WILD_TOPICS[6], QoS.EXACTLY_ONCE) });
                connection.publish(TOPICS[1], QOS1_MESSAGE, QoS.AT_LEAST_ONCE, false);
                connection.publish(TOPICS[3], QOS1_MESSAGE, QoS.EXACTLY_ONCE, false);
                connection.disconnect();

                connection = getBlockingConnection(MYCLIENTID, false);
                connection.connect();
                succeeded = receiveExpectedMessages(connection, 2);
            } catch (Exception e) {
                LOG.error(e.getMessage(), e);
                succeeded = false;
            } finally {
                logResult(connection, succeeded);
            }
            return succeeded;
        }
    }

    private class ZeroLengthClientId implements MqttTest {
        @Override
        public boolean run() {
            boolean succeeded = true;
            BlockingConnection connection = null;
            try {
                try {
                    connection = getBlockingConnection("", false);
                    connection.connect();
                    LOG.error("Empty client id with false clean session MUST throw an exception");
                    succeeded = false;
                } catch (Exception expected) {
                }

                connection = getBlockingConnection("", true);
                connection.connect();
            } catch (Exception e) {
                LOG.error(e.getMessage(), e);
                succeeded = false;
            } finally {
                logResult(connection, succeeded);
            }
            return succeeded;
        }
    }

    private class DollarTopics implements MqttTest {
        @Override
        public boolean run() {
            boolean succeeded = true;
            BlockingConnection connection = getBlockingConnection(MYCLIENTID, true);
            try {
                connection.connect();
                connection.subscribe(new Topic[] { new Topic(WILD_TOPICS[5], QoS.EXACTLY_ONCE) });
                receiveMessages(connection);

                connection.publish("$" + TOPICS[1], EMPTY_MESSAGE, QoS.AT_LEAST_ONCE, false);
                succeeded = receiveExpectedMessages(connection, 0);
            } catch (Exception e) {
                LOG.error(e.getMessage(), e);
                succeeded = false;
            } finally {
                logResult(connection, succeeded);
            }
            return succeeded;
        }
    }

    private class SubscribeFailure implements MqttTest {
        @Override
        public boolean run() {
            boolean succeeded = true;
            BlockingConnection connection = getBlockingConnection(MYCLIENTID, true);
            try {
                connection.connect();
                try {
                    final byte[] subscribe = connection
                            .subscribe(new Topic[] { new Topic(NO_SUBSCRIBE_TOPIC, QoS.AT_LEAST_ONCE) });
                    if (subscribe[0] != (byte) 0x80) {
                        LOG.error("Expected return code 0x80 when subscribing to topic " + NO_SUBSCRIBE_TOPIC);
                        succeeded = false;
                    }
                } catch (Exception expected) {
                }
            } catch (Exception e) {
                LOG.error(e.getMessage(), e);
                succeeded = false;
            } finally {
                logResult(connection, succeeded);
            }
            return succeeded;
        }
    }

    public static interface MqttTest {
        boolean run();
    }

    private BlockingConnection getBlockingConnection(String clientId, boolean cleanSession) {
        MQTT mqtt1 = new MQTT(mqtt);
        mqtt1.setCleanSession(cleanSession);
        mqtt1.setClientId(clientId);
        mqtt1.setKeepAlive((short) 5);

        return mqtt1.blockingConnection();
    }

    private boolean receiveExpectedMessages(BlockingConnection connection, int expected) throws Exception {
        int count = receiveMessages(connection);

        if (count != expected) {
            LOG.error("Expected " + expected + " messages, received " + count);
            return false;
        }
        return true;
    }

    private int receiveMessages(BlockingConnection connection) throws Exception {
        int count = 0;
        Message msg;
        while ((msg = connection.receive(RECEIVE_TIMEOUT, TimeUnit.MILLISECONDS)) != null) {
            msg.ack();
            count++;
        }
        return count;
    }

    private void logResult(BlockingConnection connection, boolean succeeded) {
        safeDisconnect(connection);
        final StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace();
        final String name = stackTrace[3].getClassName();
        StringBuffer buffer = getTestName(name);
        if (succeeded) {
            LOG.info(buffer.toString() + " test succeeded");
        } else {
            LOG.error(buffer.toString() + " test failed!");
        }
    }

    private StringBuffer getTestName(String className) {
        StringBuffer buffer = new StringBuffer();
        boolean first = true;
        for (char c : className.substring(className.lastIndexOf('$') + 1).toCharArray()) {
            if (Character.isUpperCase(c)) {
                if (!first) {
                    buffer.append(' ');
                    buffer.append(Character.toLowerCase(c));
                } else {
                    buffer.append(c);
                    first = false;
                }
            } else {
                buffer.append(c);
            }
        }
        return buffer;
    }

    private void safeDisconnect(BlockingConnection connection) {
        if (connection.isConnected()) {
            try {
                receiveMessages(connection);
                connection.disconnect();
                while (connection.isConnected()) {
                    Thread.sleep(1000);
                }
            } catch (Exception ignore) {
            }
        }
    }

}