package org.playorm.nio.impl.cm.basic;
import java.io.IOException;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.net.SocketException;
import java.nio.ByteBuffer;
import java.nio.channels.SelectableChannel;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.net.ssl.SSLEngine;
import org.playorm.nio.api.channels.NioException;
import org.playorm.nio.api.channels.TCPChannel;
import org.playorm.nio.api.deprecated.ConnectionCallback;
import org.playorm.nio.api.handlers.FutureOperation;
import org.playorm.nio.api.libs.BufferFactory;
import org.playorm.nio.api.testutil.chanapi.ChannelsFactory;
import org.playorm.nio.api.testutil.chanapi.SocketChannel;
import org.playorm.nio.impl.util.UtilWaitForConnect;
/**
* @author Dean Hiller
*/
class BasTCPChannel extends BasChannelImpl implements TCPChannel {
private static final Logger apiLog = Logger.getLogger(TCPChannel.class.getName());
private static final Logger log = Logger.getLogger(BasTCPChannel.class.getName());
private org.playorm.nio.api.testutil.chanapi.SocketChannel channel;
public BasTCPChannel(IdObject id, ChannelsFactory factory, BufferFactory bufFactory,
SelectorManager2 selMgr) throws IOException {
super(id, bufFactory, selMgr);
channel = factory.open();
channel.configureBlocking(false);
}
/**
* Only used from TCPServerChannel.accept(). please keep it that way. thanks.
* @param newChan
*/
public BasTCPChannel(IdObject id, BufferFactory bufFactory,
SocketChannel newChan, SelectorManager2 selMgr) {
super(id, bufFactory, selMgr);
if(newChan.isBlocking())
throw new IllegalArgumentException(this+"TCPChannels can only be non-blocking socketChannels");
channel = newChan;
setConnecting(true);
}
protected void bindImpl2(SocketAddress address) throws IOException {
channel.bind(address);
}
/* (non-Javadoc)
* @see api.biz.xsoftware.nio.SocketChannel#isBound()
*/
public boolean isBound() {
return channel.isBound();
}
/* Should probably synchronize this? as the after if(isClosed()) is called,
* the channel may close on another thread resulting in a ClosedChannelException....
* or we could allow these threw and wrap them with a probably not a bad thing
* exception...you just got unlucky!!!!
*
* @see api.biz.xsoftware.nio.SocketChannel#write(java.nio.ByteBuffer)
*/
public int unusedOldWrite(ByteBuffer b) throws IOException {
int remain = b.remaining();
int i = 0;
while(b.hasRemaining()) {
i++;
int result = channel.write(b);
//TODO: my performance tests showed when in lightweight apps, the performance of
//this channelmanager is so good, it floods the nic's outgoing buffer and can't
//write to it....This sleep allows it to clean out it's buffer but should really
//be a register for write so the selector can notify us when we can write again....
if(i > 5) {
log.warning(this+"Having trouble writing data out. result="+result+" b="+b);
try {
Thread.sleep(50*i); //this is a backoff, so it keeps backing off more and more until 500 ms
} catch (InterruptedException e) {
log.log(Level.WARNING, this+"exception", e);
}
} else if(i > 10)
throw new RuntimeException(this+"Bug, tried to write 1000 times and could not");
}
assert b.remaining() == 0 : this+"Did not write out all bytes";
return remain;
}
protected int writeImpl(ByteBuffer b) throws IOException {
return channel.write(b);
}
public int readImpl(ByteBuffer b) throws IOException {
if(b == null)
throw new IllegalArgumentException(this+"Cannot use a null ByteBuffer");
//special code, read information in close() method
if(isClosed())
return -1;
return channel.read(b);
}
/**
* @see org.playorm.nio.impl.cm.basic.BasChannelImpl#closeImpl()
*/
@Override
protected void closeImpl() throws IOException {
channel.close();
}
public boolean isClosed() {
return channel.isClosed();
}
public boolean isConnected() {
return channel.isConnected();
}
public void oldConnect(SocketAddress addr) {
try {
oldConnectImpl(addr);
} catch (IOException e) {
throw new NioException(e);
} catch (InterruptedException e) {
throw new NioException(e);
}
}
/* (non-Javadoc)
* @see api.biz.xsoftware.nio.TCPChannel#connect(java.net.SocketAddress)
*/
public void oldConnectImpl(SocketAddress addr) throws IOException, InterruptedException {
if(isBlocking()) {
channel.connect(addr);
} else {
UtilWaitForConnect connect = new UtilWaitForConnect();
oldConnect(addr, connect);
connect.waitForConnect();
}
}
@Override
public FutureOperation connect(SocketAddress addr) {
try {
return connectImpl(addr);
} catch (IOException e) {
throw new NioException(e);
} catch (InterruptedException e) {
throw new NioException(e);
}
}
private FutureOperation connectImpl(SocketAddress addr) throws IOException, InterruptedException {
FutureConnectImpl future = new FutureConnectImpl();
if(apiLog.isLoggable(Level.FINE))
apiLog.fine(this+"Basic.connect-addr="+addr);
boolean connected = channel.connect(addr);
if(log.isLoggable(Level.FINER))
log.finer(this+"connected status="+connected);
setConnecting(true);
if(connected) {
try {
future.connected(this);
} catch(Throwable e) {
log.log(Level.WARNING, this+"Exception occurred", e);
}
} else {
getSelectorManager().registerChannelForConnect(this, future);
}
return future;
}
public void oldConnect(SocketAddress addr, ConnectionCallback c){
try {
oldConnectImpl(addr, c);
} catch (IOException e) {
throw new NioException(e);
} catch (InterruptedException e) {
throw new NioException(e);
}
}
public void oldConnectImpl(SocketAddress addr, ConnectionCallback c) throws IOException, InterruptedException {
if(c == null)
throw new IllegalArgumentException(this+"ConnectCallback cannot be null");
if(apiLog.isLoggable(Level.FINE))
apiLog.fine(this+"Basic.connect-addr="+addr);
boolean connected = channel.connect(addr);
if(log.isLoggable(Level.FINER))
log.finer(this+"connected status="+connected);
setConnecting(true);
if(connected) {
try {
c.connected(this);
} catch(Throwable e) {
log.log(Level.WARNING, this+"Exception occurred", e);
}
} else {
getSelectorManager().registerChannelForConnect(this, c);
}
}
public boolean isBlocking() {
return channel.isBlocking();
}
/* (non-Javadoc)
* @see biz.xsoftware.nio.RegisterableChannelImpl#getRealChannel()
*/
public SelectableChannel getRealChannel() {
return channel.getSelectableChannel();
}
/* (non-Javadoc)
* @see api.biz.xsoftware.nio.NetSocketChannel#setReuseAddress(boolean)
*/
public void setReuseAddress(boolean b) {
try {
channel.setReuseAddress(b);
} catch (SocketException e) {
throw new NioException(e);
}
}
/* (non-Javadoc)
* @see api.biz.xsoftware.nio.ClientSocketChannel#getRemoteAddress()
*/
public InetSocketAddress getRemoteAddress() {
InetAddress addr = channel.getInetAddress();
int port = channel.getPort();
return new InetSocketAddress(addr, port);
}
/**
*/
public InetSocketAddress getLocalAddress()
{
InetAddress addr = channel.getLocalAddress();
int port = channel.getLocalPort();
return new InetSocketAddress(addr, port);
}
public void finishConnect() throws IOException {
channel.finishConnect();
}
public void setKeepAlive(boolean b) {
try {
channel.setKeepAlive(b);
} catch (SocketException e) {
throw new NioException(e);
}
}
public boolean getKeepAlive() {
try {
return channel.getKeepAlive();
} catch (SocketException e) {
throw new NioException(e);
}
}
@Override
public FutureOperation openSSL(SSLEngine engine) {
throw new UnsupportedOperationException("should never be called");
}
@Override
public FutureOperation closeSSL() {
throw new UnsupportedOperationException("should never be called");
}
@Override
public boolean isInSslMode() {
throw new UnsupportedOperationException("should never be called");
}
}