/* * TeleStax, Open Source Cloud Communications * Copyright 2011-2016, Telestax Inc and individual contributors * by the @authors tag. * * 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 org.restcomm.media.network.netty.channel; import java.net.SocketAddress; import org.restcomm.media.network.api.AsynchronousNetworkChannel; import org.restcomm.media.network.netty.NettyNetworkManager; import com.google.common.util.concurrent.FutureCallback; import io.netty.channel.ChannelFuture; import io.netty.channel.ChannelFutureListener; import io.netty.channel.DefaultAddressedEnvelope; /** * Asynchronous network channel powered by Netty. * * @author Henrique Rosa (henrique.rosa@telestax.com) * */ public class AsyncNettyNetworkChannel<M> implements AsynchronousNetworkChannel<M> { public static final int N_THREADS = 1; private final NettyNetworkChannelGlobalContext context; private final NettyNetworkChannelFsm fsm; public AsyncNettyNetworkChannel(NettyNetworkManager networkManager) { this(new NettyNetworkChannelGlobalContext(networkManager)); } public AsyncNettyNetworkChannel(NettyNetworkChannelGlobalContext context) { this.context = context; this.fsm = NettyNetworkChannelFsmBuilder.INSTANCE.build(this.context); this.fsm.start(); } @Override public boolean isOpen() { final NettyNetworkChannelState state = this.fsm.getCurrentState(); switch (state) { case UNINITIALIZED: case OPENING: case CLOSING: case CLOSED: return false; default: return true; } } @Override public boolean isBound() { final NettyNetworkChannelState state = this.fsm.getCurrentState(); switch (state) { case BOUND: case CONNECTING: case CONNECTED: return true; default: return false; } } @Override public SocketAddress getLocalAddress() { return isBound() ? this.context.getLocalAddress() : null; } @Override public boolean isConnected() { return NettyNetworkChannelState.CONNECTED.equals(this.fsm.getCurrentState()); } @Override public SocketAddress getRemoteAddress() { return isConnected() ? this.context.getRemoteAddress() : null; } @Override public void open(FutureCallback<Void> callback) { if (isOpen()) { // TODO handle inside FSM Listener callback.onFailure(new IllegalStateException("Channel is already open.")); } else { NettyNetworkChannelTransitionContext transitionContext = new NettyNetworkChannelTransitionContext().setCallback(callback); this.fsm.fire(NettyNetworkChannelEvent.OPEN, transitionContext); } } @Override public void close(FutureCallback<Void> callback) { if (isOpen()) { NettyNetworkChannelTransitionContext transitionContext = new NettyNetworkChannelTransitionContext().setCallback(callback); this.fsm.fire(NettyNetworkChannelEvent.CLOSE, transitionContext); } else { // TODO handle inside FSM Listener callback.onFailure(new IllegalStateException("Channel is already closed.")); } } @Override public void bind(SocketAddress localAddress, FutureCallback<Void> callback) { if (isBound()) { // TODO handle inside FSM Listener callback.onFailure(new IllegalStateException("Channel is already bound.")); } else { this.context.setLocalAddress(localAddress); NettyNetworkChannelTransitionContext transitionContext = new NettyNetworkChannelTransitionContext().setCallback(callback); this.fsm.fire(NettyNetworkChannelEvent.BIND, transitionContext); } } @Override public void connect(SocketAddress remoteAddress, FutureCallback<Void> callback) { if (isConnected()) { // TODO handle inside FSM Listener callback.onFailure(new IllegalStateException("Channel is already bound.")); } else { this.context.setRemoteAddress(remoteAddress); NettyNetworkChannelTransitionContext transitionContext = new NettyNetworkChannelTransitionContext().setCallback(callback); this.fsm.fire(NettyNetworkChannelEvent.CONNECT, transitionContext); } } @Override public void disconnect(FutureCallback<Void> callback) { if (isConnected()) { NettyNetworkChannelTransitionContext transitionContext = new NettyNetworkChannelTransitionContext().setCallback(callback); this.fsm.fire(NettyNetworkChannelEvent.DISCONNECT, transitionContext); } else { // TODO handle inside FSM Listener callback.onFailure(new IllegalStateException("Channel is not connected.")); } } @Override public void receive() { if (isBound()) { this.context.getChannel().read(); } } @Override public void send(M message, FutureCallback<Void> callback) { if (isConnected()) { final ChannelFuture future = this.context.getChannel().writeAndFlush(message); future.addListener(new NettyNetworkChannelVoidCallbackListener(callback)); } else { callback.onFailure(new IllegalStateException("Channel is not connected.")); } } @Override public void send(M message, SocketAddress remoteAddress, FutureCallback<Void> callback) { if (isBound()) { if (isConnected()) { callback.onFailure(new IllegalStateException("Channel is connected. Cannot send traffic to another peer.")); } else { DefaultAddressedEnvelope<M, SocketAddress> envelope = new DefaultAddressedEnvelope<>(message, remoteAddress); final ChannelFuture future = this.context.getChannel().writeAndFlush(envelope); future.addListener(new NettyNetworkChannelVoidCallbackListener(callback)); } } else { callback.onFailure(new IllegalStateException("Channel is not bound.")); } } static final class NettyNetworkChannelVoidCallbackListener implements ChannelFutureListener { private final FutureCallback<Void> observer; public NettyNetworkChannelVoidCallbackListener(FutureCallback<Void> observer) { super(); this.observer = observer; } @Override public void operationComplete(ChannelFuture future) throws Exception { if (future.isSuccess()) { this.observer.onSuccess(null); } else { this.observer.onFailure(future.cause()); } } } }