gash.router.client.CommConnection.java Source code

Java tutorial

Introduction

Here is the source code for gash.router.client.CommConnection.java

Source

/*
 * copyright 2016, gash
 * 
 * Gash 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 gash.router.client;

import gash.router.server.CommandInit;
import io.netty.bootstrap.Bootstrap;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioSocketChannel;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import routing.Pipe.CommandMessage;

import java.util.concurrent.LinkedBlockingDeque;
import java.util.concurrent.atomic.AtomicReference;

/**
 * provides an abstraction of the communication to the remote server.
 *
 * @author gash
 */
public class CommConnection {
    protected static Logger logger = LoggerFactory.getLogger("connect");

    protected static AtomicReference<CommConnection> instance = new AtomicReference<CommConnection>();

    private String host;
    private int port;
    private ChannelFuture channel; // do not use directly call connect()!

    private EventLoopGroup group;

    // our surge protection using a in-memory cache for messages
    LinkedBlockingDeque<CommandMessage> outbound;

    // message processing is delegated to a threading model
    private CommWorker worker;

    /**
     * Create a connection instance to this host/port. On construction the
     * connection is attempted.
     *
     * @param host
     * @param port
     */
    protected CommConnection(String host, int port) {
        this.host = host;
        this.port = port;
        init();
    }

    public static CommConnection initConnection(String host, int port) {
        instance.compareAndSet(null, new CommConnection(host, port));
        System.out.println("Printing instance " + instance.get());
        return instance.get();
    }

    public static CommConnection getInstance() throws NullPointerException {
        return instance.get();
    }

    /**
     * release all resources
     */
    public void release() {
        channel.cancel(true);
        if (channel.channel() != null)
            channel.channel().close();
        group.shutdownGracefully();
    }

    /**
     * enqueue a message to write - note this is asynchronous. This allows us to
     * inject behavior, routing, and optimization
     *
     * @param req The request
     * @throws Exception exception is raised if the message cannot be enqueued.
     */
    public void enqueue(CommandMessage req) throws Exception {
        // enqueue message
        outbound.put(req);
    }

    /**
     * messages pass through this method (no queueing). We use a blackbox design
     * as much as possible to ensure we can replace the underlining
     * communication without affecting behavior.
     * <p>
     * NOTE: Package level access scope
     *
     * @param msg
     * @return
     */
    public boolean write(CommandMessage msg) {
        if (msg == null)
            return false;
        else if (channel == null)
            throw new RuntimeException("missing channel");

        // TODO a queue is needed to prevent overloading of the socket
        // connection. For the demonstration, we don't need it
        ChannelFuture cf = connect().writeAndFlush(msg);
        if (cf.isDone() && !cf.isSuccess()) {
            logger.error("failed to send message to server");
            return false;
        }

        return true;
    }

    /**
     * abstraction of notification in the communication
     *
     * @param listener
     */
    public void addListener(CommListener listener) {
        CommHandler handler = connect().pipeline().get(CommHandler.class);
        if (handler != null)
            handler.addListener(listener);
    }

    private void init() {
        System.out.println("--> initializing connection to " + host + ":" + port);
        // the queue to support client-side surging
        outbound = new LinkedBlockingDeque<CommandMessage>();
        group = new NioEventLoopGroup();
        try {
            CommInit si = new CommInit(false);
            Bootstrap b = new Bootstrap();
            b.group(group).channel(NioSocketChannel.class).handler(si);
            b.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 10000);
            b.option(ChannelOption.TCP_NODELAY, true);
            b.option(ChannelOption.SO_KEEPALIVE, true);

            // Make the connection attempt.
            channel = b.connect(host, port).syncUninterruptibly();

            System.out.println("Printing channel " + channel);

            // want to monitor the connection to the server s.t. if we loose the
            // connection, we can try to re-establish it.
            ClientClosedListener ccl = new ClientClosedListener(this);
            channel.channel().closeFuture().addListener(ccl);
            System.out.println(channel.channel().localAddress() + " -> open: " + channel.channel().isOpen()
                    + ", write: " + channel.channel().isWritable() + ", reg: " + channel.channel().isRegistered());
        } catch (Throwable ex) {
            logger.error("failed to initialize the client connection", ex);
            ex.printStackTrace();
        }

        // start outbound message processor
        worker = new CommWorker(this);
        worker.setDaemon(true);
        worker.start();
    }

    /**
     * create connection to remote server
     *
     * @return
     */
    public Channel connect() {
        // Start the connection attempt.
        if (channel == null) {
            init();
        }

        if (channel != null && channel.isSuccess() && channel.channel().isWritable())
            return channel.channel();
        else
            throw new RuntimeException("Not able to establish connection to server");
    }

    /**
     * usage:
     * <p>
     * <pre>
     * channel.getCloseFuture().addListener(new ClientClosedListener(queue));
     * </pre>
     *
     * @author gash
     */
    public static class ClientClosedListener implements ChannelFutureListener {
        CommConnection cc;

        public ClientClosedListener(CommConnection cc) {
            this.cc = cc;
        }

        @Override
        public void operationComplete(ChannelFuture future) throws Exception {
            // we lost the connection or have shutdown.
            System.out.println("--> client lost connection to the server");
            System.out.flush();

            // @TODO if lost, try to re-establish the connection
        }
    }
}