com.mobius.software.mqtt.performance.controller.client.Client.java Source code

Java tutorial

Introduction

Here is the source code for com.mobius.software.mqtt.performance.controller.client.Client.java

Source

/**
 * Mobius Software LTD
 * Copyright 2015-2016, Mobius Software LTD
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 */

package com.mobius.software.mqtt.performance.controller.client;

import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.util.List;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.atomic.AtomicReference;

import com.mobius.software.mqtt.parser.avps.ConnackCode;
import com.mobius.software.mqtt.parser.avps.MessageType;
import com.mobius.software.mqtt.parser.avps.SubackCode;
import com.mobius.software.mqtt.parser.avps.Text;
import com.mobius.software.mqtt.parser.avps.Topic;
import com.mobius.software.mqtt.parser.avps.Will;
import com.mobius.software.mqtt.parser.header.api.MQDevice;
import com.mobius.software.mqtt.parser.header.api.MQMessage;
import com.mobius.software.mqtt.parser.header.impl.*;
import com.mobius.software.mqtt.performance.commons.data.Command;
import com.mobius.software.mqtt.performance.commons.data.ErrorType;
import com.mobius.software.mqtt.performance.commons.util.CommandParser;
import com.mobius.software.mqtt.performance.controller.Orchestrator;
import com.mobius.software.mqtt.performance.controller.net.ConnectionListener;
import com.mobius.software.mqtt.performance.controller.net.NetworkHandler;
import com.mobius.software.mqtt.performance.controller.task.MessageResendTimer;
import com.mobius.software.mqtt.performance.controller.task.TimedTask;

import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelFuture;

public class Client implements MQDevice, ConnectionListener, TimedTask {
    private String clientID;
    private AtomicBoolean status = new AtomicBoolean();
    private ConnectionContext ctx;
    private NetworkHandler listener;

    private ConcurrentLinkedQueue<Command> commands = new ConcurrentLinkedQueue<>();
    private AtomicReference<Command> pendingCommand = new AtomicReference<>();
    private AtomicReference<ChannelFuture> channelHandler = new AtomicReference<ChannelFuture>();

    private TimersMap timers;
    private AtomicLong timestamp;
    private Orchestrator orchestrator;

    private AtomicInteger failedCommands = new AtomicInteger(0);
    private IdentityReport report;

    public Client(String clientID, Orchestrator orchestrator, NetworkHandler listener,
            ConcurrentLinkedQueue<Command> commands) {
        this.clientID = clientID;
        InetSocketAddress serverAddress = orchestrator.getProperties().isWs()
                ? orchestrator.getProperties().getWsServerAddress()
                : orchestrator.getProperties().getServerAddress();
        this.ctx = new ConnectionContext(serverAddress, orchestrator.getProperties().getResendInterval());
        this.listener = listener;
        this.commands = commands;
        this.report = new IdentityReport(clientID);
        this.timers = new TimersMap(ctx, orchestrator.getScheduler(), listener, report);
        this.orchestrator = orchestrator;
        this.timestamp = new AtomicLong(System.currentTimeMillis() + orchestrator.getProperties().getInitialDelay()
                + orchestrator.getProperties().getScenarioDelay());
    }

    @Override
    public Boolean execute() {
        try {
            if (!status.get()) {
                Boolean previouslyNull = (channelHandler.get() == null);
                if (!previouslyNull) {
                    ChannelFuture future = channelHandler.get();
                    if (future.isDone()) {
                        if (future.isSuccess()) {
                            ctx.updateLocalAddress(listener.finishConnection(future, this));
                            status.set(true);
                        } else
                            report.reportError(ErrorType.CONNECTION_LOST, "failed to establish TCP connection");
                    }
                } else
                    channelHandler.compareAndSet(null, listener.connect(ctx.remoteAddress()));
                timestamp.set(System.currentTimeMillis() + orchestrator.getProperties().getInitialDelay());

                return true;
            } else {
                Long currTimestamp = timestamp.get();
                Command nextCommand = null;
                boolean firstIteration = true;
                do {
                    nextCommand = commands.poll();
                    if (nextCommand != null) {
                        if (firstIteration) {
                            Command previous = pendingCommand.getAndSet(nextCommand);
                            if (previous != null && nextCommand.getSendTime() > 0) {
                                report.reportError(ErrorType.PREVIOUS_COMMAND_FAILED,
                                        previous.getType().toString());
                                failedCommands.incrementAndGet();
                                if (!orchestrator.getProperties().isContinueOnError()) {
                                    listener.close(ctx.localAddress());
                                    return false;
                                }
                            }
                        }

                        MQMessage message = CommandParser.toMessage(nextCommand, clientID);
                        Boolean closeChannel = false;
                        switch (message.getType()) {
                        case DISCONNECT:
                            timers.stopAllTimers();
                            pendingCommand.set(null);
                            closeChannel = true;
                            break;
                        case PUBLISH:
                            Publish pub = (Publish) message;
                            switch (pub.getTopic().getQos()) {
                            case AT_MOST_ONCE:
                                pendingCommand.set(null);
                                break;
                            case AT_LEAST_ONCE:
                            case EXACTLY_ONCE:
                                timers.store(pub);
                                break;
                            default:
                                break;
                            }
                            break;
                        case SUBSCRIBE:
                        case UNSUBSCRIBE:
                            timers.store(message);
                            break;
                        case CONNECT:
                            Connect connect = (Connect) message;
                            ctx.update(connect.isCleanSession(), connect.getKeepalive(), connect.getClientID());
                            //timers.storeConnect(message);
                            break;
                        default:
                            break;
                        }
                        report.countOut(message.getType());
                        listener.send(ctx.localAddress(), message);

                        if (closeChannel) {
                            listener.releaseLocalPort(ctx.remoteAddress(),
                                    ((InetSocketAddress) ctx.localAddress()).getPort());
                            status.set(false);
                            channelHandler.set(null);
                            listener.close(ctx.localAddress());
                        }
                        Command next = commands.peek();
                        if (next != null)
                            timestamp.addAndGet(next.getSendTime());
                        else
                            timestamp.set(
                                    System.currentTimeMillis() + orchestrator.getProperties().getInitialDelay());

                        firstIteration = false;
                    } else
                        orchestrator.notifyOnComplete();
                } while (nextCommand != null && currTimestamp == timestamp.get());

                return commands.peek() != null;
            }
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    @Override
    public Long getRealTimestamp() {
        return timestamp.get();
    }

    public Command checkNext() {
        return commands.peek();
    }

    @Override
    public void processConnack(ConnackCode code, boolean sessionPresent) {
        orchestrator.notifyOnStart();
        TimedTask timer = timers.retrieveConnect();
        if (timer != null)
            timer.stop();

        if (ctx.getCleanSession() && sessionPresent)
            throw new MQTTException(MessageType.CONNACK, "invalid connack sessionPresent flag");

        switch (code) {
        case ACCEPTED:
            pendingCommand.set(null);
            timers.restartPing();
            break;
        case BAD_USER_OR_PASS:
        case IDENTIFIER_REJECTED:
        case NOT_AUTHORIZED:
        case SERVER_UNUVALIABLE:
        case UNACCEPTABLE_PROTOCOL_VERSION:
        default:
            listener.close(ctx.localAddress());
            throw new MQTTException(MessageType.CONNACK, "server rejected connection: " + code);
        }
    }

    @Override
    public void processSuback(Integer packetID, List<SubackCode> codes) {
        MessageResendTimer timer = timers.remove(packetID);
        if (timer == null || timer.retrieveMessage().getType() != MessageType.SUBSCRIBE)
            report.reportError(ErrorType.SUBACK, "invalid packet identifier");
        timers.restartPing();

        Subscribe subscribe = (Subscribe) timer.retrieveMessage();
        Topic[] topics = subscribe.getTopics();
        if (topics.length != codes.size())
            report.reportError(ErrorType.SUBACK,
                    "Invalid codes length: " + codes.size() + ", expected" + topics.length);

        pendingCommand.set(null);
    }

    @Override
    public void processUnsuback(Integer packetID) {
        MessageResendTimer timer = timers.remove(packetID);
        if (timer == null || timer.retrieveMessage().getType() != MessageType.UNSUBSCRIBE)
            report.reportError(ErrorType.UNSUBACK, "invalid packet identifier");

        timers.restartPing();
        pendingCommand.set(null);
    }

    @Override
    public void processPublish(Integer packetID, Topic topic, ByteBuf content, boolean retain, boolean isDup) {
        if (isDup) {
            report.countDuplicateIn();
            report.reportError(ErrorType.DUPLICATE, "PUBLISH received");
        }
        MQMessage response = null;
        switch (topic.getQos()) {
        case AT_LEAST_ONCE:
            response = new Puback(packetID);
            break;
        case EXACTLY_ONCE:
            response = new Pubrec(packetID);
            break;
        case AT_MOST_ONCE:
        default:
            break;
        }

        if (response != null) {
            listener.send(ctx.localAddress(), response);
            report.countOut(response.getType());
            pendingCommand.set(null);
        }
    }

    @Override
    public void processPuback(Integer packetID) {
        MessageResendTimer timer = timers.remove(packetID);
        if (timer == null || timer.retrieveMessage().getType() != MessageType.PUBLISH)
            report.reportError(ErrorType.PUBACK, "invalid packet identifier");

        timers.restartPing();
        pendingCommand.set(null);
    }

    @Override
    public void processPubrec(Integer packetID) {
        Pubrel pubrel = new Pubrel(packetID);
        MessageResendTimer oldTimer = timers.store(pubrel);
        if (oldTimer == null || oldTimer.retrieveMessage().getType() != MessageType.PUBLISH)
            report.reportError(ErrorType.PUBREC, "invalid packet identifier");
        listener.send(ctx.localAddress(), pubrel);
        report.countOut(pubrel.getType());
        pendingCommand.set(null);
    }

    @Override
    public void processPubrel(Integer packetID) {
        Pubcomp pubcomp = new Pubcomp(packetID);
        listener.send(ctx.localAddress(), pubcomp);
        report.countOut(pubcomp.getType());
        pendingCommand.set(null);
    }

    @Override
    public void processPubcomp(Integer packetID) {
        MessageResendTimer timer = timers.remove(packetID);
        if (timer == null || timer.retrieveMessage().getType() != MessageType.PUBREL)
            report.reportError(ErrorType.PUBCOMP, "invalid packet identifier");

        pendingCommand.set(null);
    }

    @Override
    public void processPingresp() {
        Command nextCommand = checkNext();
        if (nextCommand != null && getRealTimestamp() > (System.currentTimeMillis()
                + orchestrator.getProperties().getMinPingInterval() + ctx.getKeepalive() * 1000))
            timers.restartPing();
        else
            timers.stopPing();
    }

    @Override
    public void processConnect(boolean cleanSession, int keepalive, Will will) {
        report.reportError(ErrorType.CONNECT, "Received invalid message from broker");
    }

    @Override
    public void processSubscribe(Integer packetID, Topic[] topics) {
        report.reportError(ErrorType.SUBSCRIBE, "Received invalid message from broker");
    }

    @Override
    public void processUnsubscribe(Integer packetID, Text[] topics) {
        report.reportError(ErrorType.UNSUBSCRIBE, "Received invalid message from broker");
    }

    @Override
    public void processPingreq() {
        report.reportError(ErrorType.PINGREQ, "Received invalid message from broker");
    }

    @Override
    public void processDisconnect() {
        report.reportError(ErrorType.DISCONNECT, "Received invalid message from broker");
    }

    @Override
    public void connectionDown(SocketAddress address) {
        if (status.get()) {
            //report.reportError(ErrorType.CONNECTION_LOST, "connection closed:" + address);
            timers.stopAllTimers();
        }
    }

    @Override
    public void packetReceived(SocketAddress address, MQMessage message) {
        try {
            report.countIn(message.getType());
            message.processBy(this);
        } catch (MQTTException e) {
            report.reportError(ErrorType.forMessageType(e.getType()), e.getMessage());
        }
    }

    @Override
    public void stop() {
        if (ctx.localAddress() != null)
            listener.close(ctx.localAddress());

        timers.stopAllTimers();
    }

    public IdentityReport retrieveReport() {
        int commandsCount = commands.size() + failedCommands.get();
        if (pendingCommand.get() != null)
            commandsCount++;

        report.setUnfinishedCommands(commandsCount);
        return report;
    }

    public String retrieveIdentifier() {
        return this.clientID;
    }
}