package org.async.rmi.netty;
import io.netty.bootstrap.Bootstrap;
import io.netty.channel.Channel;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelFutureListener;
import io.netty.util.AttributeKey;
import org.async.rmi.Connection;
import org.async.rmi.client.ClientResultSet;
import org.async.rmi.client.ClosedException;
import org.async.rmi.client.RemoteObjectAddress;
import org.async.rmi.messages.Message;
import org.async.rmi.messages.Response;
import org.async.rmi.pool.Pool;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
/**
* Created by Barak Bar Orion
* 27/10/14.
*/
public class NettyClientConnection implements Connection<Message> {
private static final Logger logger = LoggerFactory.getLogger(NettyClientConnection.class);
public static final AttributeKey<Object> ATTACH_KEY = AttributeKey.valueOf("ATTACH_KEY");
private final Bootstrap bootstrap;
private Pool<Connection<Message>> pool;
private RemoteObjectAddress address;
private ChannelFuture channelFuture;
private volatile boolean closed = false;
private String localAddress;
private String remoteAddress;
public NettyClientConnection(Bootstrap bootstrap, RemoteObjectAddress address, Pool<Connection<Message>> pool) {
this.bootstrap = bootstrap;
this.address = address;
this.pool = pool;
}
public CompletableFuture<Connection<Message>> connect() {
final CompletableFuture<Connection<Message>> res = new CompletableFuture<>();
channelFuture = bootstrap.connect(address.getHost(), address.getPort());
channelFuture.addListener((ChannelFuture future) -> {
try {
initAddresses(future);
Channel channel = future.sync().channel();
// To prevent premature message from the client to the server
// resolve the connection future only when all handshakes are done.
RMIClientHandler clientHandler = channel.pipeline().get(RMIClientHandler.class);
clientHandler.getHandshakeCompleteFuture().whenComplete((aVoid, throwable) -> {
if (throwable != null) {
res.completeExceptionally(throwable);
} else {
res.complete(NettyClientConnection.this);
}
});
channel.closeFuture().addListener(cf -> {
closed = true;
pool.free(NettyClientConnection.this);
Object attachment = attach();
if(attachment != null){
clearAttachment();
}
if(attachment instanceof ClientResultSet){
((ClientResultSet)attachment).onConnectionClosed();
}
res.completeExceptionally(new ClosedException());
});
} catch (Exception e) {
//noinspection ConstantConditions
if (!(e instanceof java.net.ConnectException)) {
logger.error(e.toString(), e);
}
}
});
return res;
}
@Override
public void attach(Object value) throws InterruptedException {
channelFuture.sync().channel().attr(ATTACH_KEY).set(value);
}
@Override
public Object attach() throws InterruptedException {
return channelFuture.sync().channel().attr(ATTACH_KEY).get();
}
@Override
public void clearAttachment() throws InterruptedException {
channelFuture.sync().channel().attr(ATTACH_KEY).remove();
}
private void initAddresses(ChannelFuture future){
try {
Channel channel = future.sync().channel();
InetSocketAddress la = (InetSocketAddress) channel.localAddress();
localAddress = la.getHostString() + ":" + la.getPort();
InetSocketAddress ra = (InetSocketAddress) channel.remoteAddress();
remoteAddress = ra.getHostString() + ":" + ra.getPort();
}catch(Exception ignored){
}
}
@Override
public void free() {
pool.free(this);
}
@Override
public boolean isClosed() {
return closed;
}
@Override
public void close() throws IOException {
closed = true;
channelFuture.addListener((ChannelFuture future) -> future.channel().close());
}
@Override
public void send(Message message) {
channelFuture.addListener((ChannelFuture future) -> future.channel().writeAndFlush(message));
}
@Override
public void sendOneWay(Message message, CompletableFuture<Response> response) {
channelFuture.addListener((ChannelFuture future) -> future.channel().writeAndFlush(message)
.addListener((ChannelFuture future1) -> {
try {
future1.get();
response.complete(null);
} catch (ExecutionException e) {
response.completeExceptionally(e.getCause());
} catch (Exception e) {
response.completeExceptionally(e);
}
}
));
}
public String getRemoteAddress() {
return remoteAddress;
}
public String getLocalAddress() {
return localAddress;
}
}