io.hydramq.network.client.AbstractConnection.java Source code

Java tutorial

Introduction

Here is the source code for io.hydramq.network.client.AbstractConnection.java

Source

/*
 * The MIT License (MIT)
 *
 * Copyright  2016-, Boku Inc., Jimmie Fulton
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
 * documentation files (the "Software"), to deal in the Software without restriction, including without limitation the
 * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit
 * persons to whom the Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the
 * Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
 * WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
 * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
 * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 */

package io.hydramq.network.client;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

import io.hydramq.core.net.Command;
import io.hydramq.core.net.netty.ChannelAttributes;
import io.hydramq.exceptions.HydraConnectionException;
import io.hydramq.exceptions.HydraDisconnectException;
import io.hydramq.exceptions.HydraRuntimeException;
import io.hydramq.internal.util.Assert;
import io.hydramq.listeners.Listen;
import io.hydramq.network.ConnectionState;
import io.hydramq.network.ConnectionStateListener;
import io.netty.bootstrap.Bootstrap;
import io.netty.channel.Channel;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioSocketChannel;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import static io.hydramq.listeners.Listen.ONCE;

/**
 * @author jfulton
 */
public abstract class AbstractConnection implements Connection {

    private static final Logger logger = LoggerFactory.getLogger(AbstractConnection.class);
    private Lock connectionLock = new ReentrantLock();
    private Condition connectedCondition = connectionLock.newCondition();
    private AtomicBoolean connecting = new AtomicBoolean(false);
    private EventLoopGroup eventLoopGroup = new NioEventLoopGroup();
    private volatile Channel channel;
    private Bootstrap bootstrap;
    private InetSocketAddress endpoint;
    private CompletableFuture<Void> closeFuture = new CompletableFuture<>();
    private Map<ConnectionStateListener, Listen> disconnectionListeners = new HashMap<>();

    public AbstractConnection() {
        this.bootstrap = new Bootstrap();
        bootstrap.group(eventLoopGroup).channel(NioSocketChannel.class).handler(channelInitializer());
    }

    @Override
    public CompletableFuture<Void> connect(final InetSocketAddress endpoint) {
        Assert.argumentNotNull(endpoint, "endpoint");
        final CompletableFuture<Void> connectFuture = new CompletableFuture<>();

        // Already connected, or a connection is in progress.  Keep the existing endpoint.
        if (isConnected() || connecting.compareAndSet(true, false)) {
            connectFuture.completeExceptionally(
                    new HydraRuntimeException("Already connected or connecting to " + this.endpoint));
            return connectFuture;
        }

        this.endpoint = endpoint;

        final ChannelFuture bootstrapConnect = bootstrap().connect(endpoint);
        channel = bootstrapConnect.channel();
        channel.attr(ChannelAttributes.COMMAND_FUTURES).set(new ConcurrentHashMap<>());
        bootstrapConnect.addListener(f -> {
            if (f.isSuccess()) {
                handshake().whenComplete((aVoid, throwable) -> {
                    List<ConnectionStateListener> handshakeListeners = new ArrayList<>();
                    try {
                        connectionLock.lock();

                        if (throwable != null) {
                            // We connected, but there was a handshake error.
                            channel().disconnect().addListener(closeFuture -> {
                                connecting.set(false);
                                connectFuture.completeExceptionally(
                                        new HydraRuntimeException("Handshaking error", throwable));
                                handshakeListeners.addAll(toNotify());
                            });
                        } else { // We successfully connected, and handshake was also successful.
                            channel().closeFuture().addListener(closeFuture -> { // Register disconnect future.  Disconnect can happen for any reason.
                                List<ConnectionStateListener> closeListeners = new ArrayList<>();
                                try {
                                    connectionLock.lock();
                                    // Clean up pending futures.
                                    for (CompletableFuture<Command> pendingResponse : channel()
                                            .attr(ChannelAttributes.COMMAND_FUTURES).get().values()) {
                                        pendingResponse
                                                .completeExceptionally(new HydraDisconnectException(endpoint));
                                    }
                                    closeListeners.addAll(toNotify());
                                } finally {
                                    connectionLock.unlock();
                                }
                                closeListeners.forEach(
                                        listener -> listener.onStateChanged(this, ConnectionState.DISCONNECTED));
                            });
                            connecting.set(false);
                            connectedCondition.signalAll();

                            logger.info("Connected to {}!", endpoint);
                            connectFuture.complete(null);
                        }
                    } finally {
                        connectionLock.unlock();
                    }
                    handshakeListeners.forEach(disconnectionListener -> disconnectionListener.onStateChanged(this,
                            ConnectionState.DISCONNECTED));
                });
            } else {
                List<ConnectionStateListener> connectFailureListeners = new ArrayList<>();
                try {
                    connectionLock.lock();
                    connecting.set(false);
                    connectFuture.completeExceptionally(new HydraConnectionException(endpoint, f.cause()));
                    connectFailureListeners.addAll(toNotify());
                } finally {
                    connectionLock.unlock();
                }
                connectFailureListeners
                        .forEach(listener -> listener.onStateChanged(this, ConnectionState.DISCONNECTED));
            }

        });
        return connectFuture;
    }

    private List<ConnectionStateListener> toNotify() {
        List<ConnectionStateListener> listeners = new ArrayList<>();
        disconnectionListeners.forEach((listener, listen) -> {
            if (listen == ONCE) {
                disconnectionListeners.remove(listener);
            }
            listeners.add(listener);
        });
        return listeners;
    }

    @Override
    public CompletableFuture<Void> disconnect() {
        CompletableFuture<Void> future = new CompletableFuture<>();
        channel().close().addListener(new ChannelFutureListener() {
            @Override
            public void operationComplete(final ChannelFuture closeFuture) throws Exception {
                if (closeFuture.isSuccess()) {
                    future.complete(null);
                    logger.info("Disconnected");
                } else {
                    future.completeExceptionally(closeFuture.cause());
                }
            }
        });
        return future;
    }

    @Override
    public void monitor(final ConnectionStateListener connectionStateListener, final Listen listen) {
        try {
            connectionLock.lock();
            if (listen == Listen.REMOVE) {
                this.disconnectionListeners.remove(connectionStateListener);
            } else if (listen == Listen.CONTINUOUSLY) {
                this.disconnectionListeners.put(connectionStateListener, listen);
                if (!isConnected()) {
                    connectionStateListener.onStateChanged(this, ConnectionState.DISCONNECTED);
                }
            } else if (listen == ONCE) {
                if (!isConnected()) {
                    connectionStateListener.onStateChanged(this, ConnectionState.DISCONNECTED);
                } else {
                    disconnectionListeners.put(connectionStateListener, listen);
                }
            }
        } finally {
            connectionLock.unlock();
        }

    }

    //    @Override
    //    public CompletableFuture<Void> close() {
    //        CompletableFuture<Void> closeFuture = new CompletableFuture<>();
    //        if (open.compareAndSet(true, false)) {
    //            connectionFailureListener = null;
    //            disconnect().thenAccept(aVoid -> eventLoopGroup.shutdownGracefully().addListener(elgFuture -> {
    //                if (elgFuture.isSuccess()) {
    //                    closeFuture.complete(null);
    //                } else {
    //                    closeFuture.completeExceptionally(elgFuture.cause());
    //                }
    //            }));
    //            return closeFuture;
    //        } else {
    //            closeFuture.completeExceptionally(new PuntException("Already closed"));
    //            return closeFuture;
    //        }
    //    }

    @Override
    public CompletableFuture<Void> closeFuture() {
        return closeFuture;
    }

    @Override
    public void close() throws IOException {
        try {
            closeFuture().complete(null);
            channel().close().await();
        } catch (InterruptedException e) {
            throw new HydraRuntimeException(e);
        }
    }

    public InetSocketAddress getEndpoint() {
        if (endpoint == null) {
            throw new HydraRuntimeException("Not connected");
        }
        return endpoint;
    }

    protected abstract CompletableFuture<Void> handshake();

    protected abstract void onCommand(ChannelHandlerContext ctx, Command command);

    protected Bootstrap bootstrap() {
        return bootstrap;
    }

    protected Channel channel() {
        return channel;
    }

    protected abstract ChannelInitializer<Channel> channelInitializer();

    public boolean isConnected() {
        return channel != null && channel().isActive();
    }

    protected void blockForConnection() {
        try {
            connectionLock.lock();
            while (!isConnected()) {
                try {
                    connectedCondition.await();
                } catch (InterruptedException e) {
                    logger.error("Interrupted", e);
                }
            }
        } finally {
            connectionLock.unlock();
        }
    }
}