package org.webpieces.nio.impl.cm.basic;
import java.io.IOException;
import java.net.BindException;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.net.SocketException;
import java.nio.channels.SelectableChannel;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import org.webpieces.util.logging.Logger;
import org.webpieces.util.logging.LoggerFactory;
import org.webpieces.data.api.BufferPool;
import org.webpieces.nio.api.channels.TCPServerChannel;
import org.webpieces.nio.api.exceptions.NioException;
import org.webpieces.nio.api.handlers.ConnectionListener;
import org.webpieces.nio.api.handlers.ConsumerFunc;
import org.webpieces.nio.api.handlers.DataListener;
import org.webpieces.nio.api.testutil.chanapi.ChannelsFactory;
/**
* @author Dean Hiller
*/
class BasTCPServerChannel extends RegisterableChannelImpl implements TCPServerChannel {
private static final Logger log = LoggerFactory.getLogger(BasTCPServerChannel.class);
private final ServerSocketChannel channel;
private final ChannelsFactory channelFactory;
private final ConnectionListener connectionListener;
private BufferPool pool;
private int channelCount = 0;
public BasTCPServerChannel(IdObject id, ChannelsFactory c, SelectorManager2 selMgr,
ConnectionListener listener, BufferPool pool) {
super(id, selMgr);
this.connectionListener = listener;
this.channelFactory = c;
this.pool = pool;
try {
channel = ServerSocketChannel.open();
channel.configureBlocking(false);
} catch(IOException e) {
throw new NioException(e);
}
}
public int getChannelCount() {
return channelCount++;
}
/* (non-Javadoc)
* @see api.biz.xsoftware.nio.TCPServerChannel#accept()
*/
public void accept(int newSocketNum) throws IOException {
try {
//special code...see information in close() method
if(isClosed())
return;
SocketChannel newChan = channel.accept();
if(newChan == null)
return;
newChan.configureBlocking(false);
org.webpieces.nio.api.testutil.chanapi.SocketChannel proxyChan = channelFactory.open(newChan);
SocketAddress remoteAddress = newChan.getRemoteAddress();
IdObject obj = new IdObject(getIdObject(), newSocketNum);
BasTCPChannel tcpChan = new BasTCPChannel(obj, proxyChan, remoteAddress, getSelectorManager(), pool);
log.trace(()->tcpChan+"Accepted new incoming connection");
CompletableFuture<DataListener> connectFuture = connectionListener.connected(tcpChan, true);
connectFuture.thenAccept(l -> tcpChan.registerForReads(l));
} catch(Throwable e) {
log.error(this+"Failed to connect", e);
connectionListener.failed(this, e);
}
}
public void registerForReads(DataListener listener) throws IOException, InterruptedException {
throw new UnsupportedOperationException("TCPServerChannel's can't read, they can only accept incoming connections");
}
public void registerServerSocketChannel(ConnectionListener cb) {
if(!isBound())
throw new IllegalArgumentException("Only bound sockets can be registered or selector doesn't work");
try {
CompletableFuture<Void> future = getSelectorManager().registerServerSocketChannel(this, cb);
future.get();
} catch (IOException e) {
throw new NioException(e);
} catch (InterruptedException e) {
throw new NioException(e);
} catch (ExecutionException e) {
throw new NioException(e);
}
}
public void bind(SocketAddress srvrAddr) {
try {
bindImpl(srvrAddr);
registerServerSocketChannel(connectionListener);
} catch (IOException e) {
throw new NioException(e);
}
}
/* (non-Javadoc)
* @see api.biz.xsoftware.nio.TCPServerChannel#bind(java.net.SocketAddress)
*/
private void bindImpl(SocketAddress srvrAddr) throws IOException {
try {
channel.socket().bind(srvrAddr);
} catch(BindException e) {
BindException ee = new BindException("bind exception on addr="+srvrAddr);
ee.initCause(e);
throw ee;
}
}
public boolean isBound() {
return channel.socket().isBound();
}
public void closeServerChannel() {
//socket.close was resulting in following exception on polling thread.
//To fix this, we put mechanisms in place to look if this channel
//was closed or not on the call to accept method
//
//INFO: [[client]][ClientChannel] READ 0 bytes(this is strange)
//Feb 19, 2006 6:01:22 AM biz.xsoftware.impl.nio.cm.basic.TCPServerChannelImpl accept
//WARNING: [[server]][TCPServerChannel] Failed to connect
//java.nio.channels.ClosedChannelException
//at sun.nio.ch.ServerSocketChannelImpl.accept(ServerSocketChannelImpl.java:130)
//at biz.xsoftware.impl.nio.cm.basic.TCPServerChannelImpl.accept(TCPServerChannelImpl.java:61)
//at biz.xsoftware.impl.nio.cm.basic.Helper.acceptSocket(Helper.java:109)
//at biz.xsoftware.impl.nio.cm.basic.Helper.processKey(Helper.java:82)
//at biz.xsoftware.impl.nio.cm.basic.Helper.processKeys(Helper.java:47)
//at biz.xsoftware.impl.nio.cm.basic.SelectorManager2.runLoop(SelectorManager2.java:305)
//at biz.xsoftware.impl.nio.cm.basic.SelectorManager2$PollingThread.run(SelectorManager2.java:267)
try {
channel.socket().close();
channel.close();
super.wakeupSelector();
} catch(Exception e) {
log.error(this+"Exception closing channel", e);
}
}
public boolean isClosed() {
return channel.socket().isClosed();
}
/**
*/
public SelectableChannel getRealChannel() {
return channel;
}
/* (non-Javadoc)
* @see api.biz.xsoftware.nio.RegisterableChannel#isBlocking()
*/
public boolean isBlocking() {
return channel.isBlocking();
}
/* (non-Javadoc)
* @see api.biz.xsoftware.nio.RegisterableChannel#setReuseAddress(boolean)
*/
public void setReuseAddress(boolean b) {
try {
channel.socket().setReuseAddress(b);
} catch (SocketException e) {
throw new NioException(e);
}
}
/* (non-Javadoc)
* @see api.biz.xsoftware.nio.SocketSuperclass#getLocalAddress()
*/
public InetSocketAddress getLocalAddress() {
if(!channel.socket().isBound())
throw new IllegalStateException("Socket not bound yet. please bind before calling getLocalAddress");
InetAddress addr = channel.socket().getInetAddress();
int port = channel.socket().getLocalPort();
return new InetSocketAddress(addr, port);
}
@Override
public void configure(ConsumerFunc<ServerSocketChannel> methodToConfigure) {
try {
if(methodToConfigure != null)
methodToConfigure.accept(channel);
} catch(Exception e) {
throw new RuntimeException(e);
}
}
@Override
public ServerSocketChannel getUnderlyingChannel() {
return channel;
}
}