com.github.mrstampy.kitchensync.netty.channel.AbstractKiSyChannel.java Source code

Java tutorial

Introduction

Here is the source code for com.github.mrstampy.kitchensync.netty.channel.AbstractKiSyChannel.java

Source

/*
 * KitchenSync-core Java Library Copyright (C) 2014 Burton Alexander
 * 
 * This program is free software; you can redistribute it and/or modify it under
 * the terms of the GNU General Public License as published by the Free Software
 * Foundation; either version 2 of the License, or (at your option) any later
 * version.
 * 
 * This program 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 General Public License for more
 * details.
 * 
 * You should have received a copy of the GNU General Public License along with
 * this program; if not, write to the Free Software Foundation, Inc., 51
 * Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 * 
 */
package com.github.mrstampy.kitchensync.netty.channel;

import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.socket.DatagramChannel;
import io.netty.channel.socket.DatagramPacket;
import io.netty.util.concurrent.GenericFutureListener;

import java.net.InetSocketAddress;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.github.mrstampy.kitchensync.message.outbound.KiSyOutboundMessageManager;
import com.github.mrstampy.kitchensync.netty.Bootstrapper;
import com.github.mrstampy.kitchensync.netty.channel.payload.ByteBufCreator;
import com.github.mrstampy.kitchensync.util.KiSyUtils;

/**
 * Abstract superclass for {@link KiSyChannel} implementations. Note that the
 * first AbstractKiSyChannel instantiated determines the channel initializer for
 * the default bootstrap. To set it explicitly call
 * {@link Bootstrapper#initDefaultBootstrap(ChannelInitializer, Class)} prior to
 * instantiating instances of this class.
 *
 * @param <BBC>
 *          the {@link ByteBufCreator} implementation
 * @param <CI>
 *          the ChannelInitializer implementation
 * @param <DC>
 *          the DatagramChannel implementaion
 */
public abstract class AbstractKiSyChannel<BBC extends ByteBufCreator, CI extends ChannelInitializer<DatagramChannel>, DC extends DatagramChannel>
        implements KiSyChannel {
    private static final Logger log = LoggerFactory.getLogger(AbstractKiSyChannel.class);

    /** The send future. */
    protected static GenericFutureListener<ChannelFuture> SEND_FUTURE = new GenericFutureListener<ChannelFuture>() {

        @Override
        public void operationComplete(ChannelFuture future) throws Exception {
            if (future.isSuccess()) {
                log.trace("Message successfully sent");
            } else {
                Throwable cause = future.cause();
                if (cause == null) {
                    log.error("Could not send message");
                } else {
                    log.error("Could not send message", cause);
                }
            }
        }
    };

    /** The connect future. */
    protected static GenericFutureListener<ChannelFuture> CONNECT_FUTURE = new GenericFutureListener<ChannelFuture>() {

        @Override
        public void operationComplete(ChannelFuture future) throws Exception {
            if (future.isSuccess()) {
                log.debug("Connection to {} successful", future.channel().remoteAddress());
            } else {
                Throwable cause = future.cause();
                if (cause == null) {
                    log.error("Could not connect");
                } else {
                    log.error("Could not connect", cause);
                }
            }
        }
    };

    /** The close future. */
    protected static GenericFutureListener<ChannelFuture> CLOSE_FUTURE = new GenericFutureListener<ChannelFuture>() {

        @Override
        public void operationComplete(ChannelFuture future) throws Exception {
            if (future.isSuccess()) {
                log.trace("Channel successfully closed");
            } else {
                Throwable cause = future.cause();
                if (cause == null) {
                    log.error("Could not close channel");
                } else {
                    log.error("Could not close channel", cause);
                }
            }
        }
    };

    /** The registry. */
    protected DefaultChannelRegistry registry = DefaultChannelRegistry.INSTANCE;

    /** The outbound manager. */
    protected KiSyOutboundMessageManager outboundManager = KiSyOutboundMessageManager.INSTANCE;

    private DC channel;

    /** The bootstrapper. */
    protected Bootstrapper bootstrapper;

    private AtomicBoolean active = new AtomicBoolean(false);

    /**
     * Implementations return the <a
     * href="http://netty.io/4.0/api/io/netty/channel/ChannelInitializer.html"
     * >channel initializer</a> required for this channel. Netty <a
     * href="http://netty.io/4.0/api/io/netty/channel/ChannelHandler.html">Channel
     * handlers</a> should be added to the channel's pipeline to control its
     * behaviour ie. <a
     * href="http://netty.io/4.0/api/io/netty/handler/ssl/SslHandler.html"
     * >SslHandler</a> can be added for SSL encryption. <br>
     * <br>
     * 
     * Note that the default bootstrap will use the ChannelInitializer from the
     * first instantiated {@link AbstractKiSyChannel}, unless it has been
     * explicitly initialized prior.
     * 
     * @return the channel initializer
     */
    protected abstract CI initializer();

    /**
     * Gets the channel class.
     *
     * @return the channel class
     */
    protected abstract Class<DC> getChannelClass();

    private BBC byteBufCreator;

    /**
     * The Constructor.
     */
    public AbstractKiSyChannel() {
        setBootstrapper(Bootstrapper.INSTANCE);
    }

    /*
     * (non-Javadoc)
     * 
     * @see com.github.mrstampy.kitchensync.netty.channel.KiSyChannel#isActive()
     */
    @Override
    public boolean isActive() {
        return active.get();
    }

    /*
     * (non-Javadoc)
     * 
     * @see com.github.mrstampy.kitchensync.netty.channel.KiSyChannel#bind()
     */
    @Override
    public void bind() {
        bindDefaultBootstrapInit();

        DC channel = bootstrapper.bind();

        setChannel(channel);

        registry.addChannel(this);
    }

    /*
     * (non-Javadoc)
     * 
     * @see com.github.mrstampy.kitchensync.netty.channel.KiSyChannel#bind(int)
     */
    @Override
    public void bind(int port) {
        bindDefaultBootstrapInit();

        DC channel = bootstrapper.bind(port);

        setChannel(channel);

        registry.addChannel(this);
    }

    /**
     * Prebind.
     */
    protected void prebind() {
        if (getByteBufCreator() == null)
            throw new IllegalStateException("The ByteBuf creator must be set");
    }

    /**
     * Creates a DatagramPacket for sending down the socket from the specified
     * message.
     *
     * @param <MSG>
     *          the generic type
     * @param message
     *          the message
     * @param address
     *          the address
     * @return the object
     * @see ByteBufCreator
     */
    protected <MSG extends Object> DatagramPacket createMessage(MSG message, InetSocketAddress address) {
        ByteBufCreator creator = getByteBufCreator();

        if (creator == null) {
            log.error("Cannot determine ByteBufCreator for message {}, recipient {}", message, address);
            return null;
        }

        return new DatagramPacket(creator.createByteBuf(message, address), address);
    }

    /*
     * (non-Javadoc)
     * 
     * @see
     * com.github.mrstampy.kitchensync.netty.channel.KiSyChannel#send(java.lang
     * .Object, java.net.InetSocketAddress)
     */
    @Override
    public <MSG extends Object> ChannelFuture send(MSG message, InetSocketAddress address) {
        presend(message, address);
        DatagramPacket msg = createMessage(message, address);
        return sendImpl(msg, address);
    }

    /**
     * Presend, invoked prior to
     * {@link #sendImpl(DatagramPacket, InetSocketAddress)}.
     *
     * @param <MSG>
     *          the generic type
     * @param message
     *          the message
     * @param address
     *          the address
     * @see KiSyOutboundMessageManager
     */
    protected <MSG extends Object> void presend(MSG message, InetSocketAddress address) {
        outboundManager.presend(message, localAddress(), address);
    }

    /**
     * Send impl.
     *
     * @param dp
     *          the dp
     * @param address
     *          the address
     * @return the channel future
     */
    protected ChannelFuture sendImpl(DatagramPacket dp, InetSocketAddress address) {
        if (!isActive()) {
            log.error("Channel is not active, cannot send {} to {}", dp, address);
            return new KiSyFailedFuture();
        }

        if (dp == null) {
            log.error("No message to send to {}", address);
            return new KiSyFailedFuture();
        }

        ChannelFuture cf = getChannel().writeAndFlush(dp);

        cf.addListener(SEND_FUTURE);

        return cf;
    }

    /*
     * (non-Javadoc)
     * 
     * @see com.github.mrstampy.kitchensync.netty.channel.KiSyChannel#getPort()
     */
    public int getPort() {
        return isActive() ? getChannel().localAddress().getPort() : -1;
    }

    /*
     * (non-Javadoc)
     * 
     * @see com.github.mrstampy.kitchensync.netty.channel.KiSyChannel#close()
     */
    @Override
    public ChannelFuture close() {
        if (!isActive())
            return new KiSyFailedFuture();

        ChannelFuture cf = getChannel().close();
        cf.addListener(CLOSE_FUTURE);

        return cf;
    }

    /*
     * (non-Javadoc)
     * 
     * @see com.github.mrstampy.kitchensync.netty.channel.KiSyChannel#getChannel()
     */
    @Override
    public DatagramChannel getChannel() {
        return channel;
    }

    /*
     * (non-Javadoc)
     * 
     * @see
     * com.github.mrstampy.kitchensync.netty.channel.KiSyChannel#localAddress()
     */
    public InetSocketAddress localAddress() {
        return new InetSocketAddress(bootstrapper.DEFAULT_ADDRESS, getPort());
    }

    /**
     * Sets the channel.
     *
     * @param channel
     *          the channel
     */
    protected void setChannel(DC channel) {
        this.channel = channel;

        channel.closeFuture().addListener(new GenericFutureListener<ChannelFuture>() {

            @Override
            public void operationComplete(ChannelFuture future) throws Exception {
                registry.removeChannel(AbstractKiSyChannel.this);
                active.set(false);
            }
        });

        active.set(true);
    }

    /**
     * Bind default bootstrap init.
     */
    protected void bindDefaultBootstrapInit() {
        prebind();

        if (isActive())
            closeChannel();
        if (!bootstrapper.hasDefaultBootstrap())
            bootstrapper.initDefaultBootstrap(initializer(), getChannelClass());
    }

    /**
     * Close channel.
     */
    protected void closeChannel() {
        ChannelFuture cf = close();
        final CountDownLatch latch = new CountDownLatch(1);
        cf.addListener(new GenericFutureListener<ChannelFuture>() {

            @Override
            public void operationComplete(ChannelFuture future) throws Exception {
                latch.countDown();
            }
        });

        await(latch, "Channel close timed out");
    }

    /**
     * Await.
     *
     * @param latch
     *          the latch
     * @param error
     *          the error
     */
    protected void await(CountDownLatch latch, String error) {
        boolean ok = KiSyUtils.await(latch, 5, TimeUnit.SECONDS);
        if (!ok)
            log.error(error);
    }

    /*
     * (non-Javadoc)
     * 
     * @see
     * com.github.mrstampy.kitchensync.netty.channel.KiSyChannel#isMulticastChannel
     * ()
     */
    @Override
    public boolean isMulticastChannel() {
        return false;
    }

    /*
     * (non-Javadoc)
     * 
     * @see
     * com.github.mrstampy.kitchensync.netty.channel.KiSyChannel#isPortSpecificChannel
     * ()
     */
    @Override
    public boolean isPortSpecificChannel() {
        return false;
    }

    /**
     * Gets the bootstrapper.
     *
     * @return the bootstrapper
     */
    public Bootstrapper getBootstrapper() {
        return bootstrapper;
    }

    /**
     * Sets the bootstrapper.
     *
     * @param bootstrapper
     *          the bootstrapper
     */
    public void setBootstrapper(Bootstrapper bootstrapper) {
        this.bootstrapper = bootstrapper;
    }

    /*
     * (non-Javadoc)
     * 
     * @see
     * com.github.mrstampy.kitchensync.netty.channel.KiSyChannel#getByteBufCreator
     * ()
     */
    public BBC getByteBufCreator() {
        if (byteBufCreator == null)
            byteBufCreator = initByteBufCreator();

        return byteBufCreator;
    }

    /**
     * Inits the byte buf creator.
     *
     * @return the bbc
     */
    protected abstract BBC initByteBufCreator();

    /**
     * Sets the byte buf creator.
     *
     * @param byteBufCreator
     *          the byte buf creator
     */
    public void setByteBufCreator(BBC byteBufCreator) {
        this.byteBufCreator = byteBufCreator;
    }

}