/*
* JacORB - a free Java ORB
*
* Copyright (C) 2011-2014 Gerald Brose / The JacORB Team.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library 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
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public
* License along with this library; if not, write to the Free
* Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
package org.jacorb.orb.nio;
import java.nio.channels.SocketChannel;
import java.nio.ByteBuffer;
import java.io.IOException;
import org.jacorb.orb.etf.StreamConnectionBase;
import org.omg.CORBA.COMM_FAILURE;
import org.jacorb.util.SelectorRequest;
import org.jacorb.util.SelectorRequestCallback;
import org.jacorb.util.SelectorManager;
import org.jacorb.config.*;
import org.omg.CORBA.TIMEOUT;
/**
* @author Ciju John
*/
public abstract class NIOConnection
extends StreamConnectionBase
{
private int timeout;
protected SocketChannel channel = null;
protected SelectorManager selectorManager = null;
private int maxConsecutiveTimeouts = 0;
protected int failedWriteAttempts = 0;
protected boolean isDebugEnabled = false;
public void configure(Configuration config)
throws ConfigurationException
{
super.configure (config);
isDebugEnabled = logger.isDebugEnabled();
selectorManager = orb.getSelectorManager ();
maxConsecutiveTimeouts = configuration.getAttributeAsInteger("jacorb.nio.maxConsecutiveTimeouts", 0);
try
{
channel = SocketChannel.open ();
}
catch (Exception ex)
{
logger.error ("Unable to initialize channel: " + ex.toString());
// can't do much more
}
}
// /* no SSL support yet */
public boolean isSSL()
{
return false;
}
@Override // ConnectionBase
public synchronized boolean is_connected()
{
if (isDebugEnabled)
{
logger.debug (this.toString() + ".is_connected()");
}
return connected && channel.isConnected();
}
protected synchronized void setConnected (boolean connected)
{
this.connected = connected;
}
@Override // ConnectionBase
protected COMM_FAILURE handleCommFailure(IOException e)
{
return to_COMM_FAILURE(e);
}
@Override // ConnectionBase
protected void setTimeout(int timeout)
{
this.timeout = timeout;
}
@Override // ConnectionBase
protected int getTimeout()
{
return timeout;
}
//boolean is_connected();
// time_out is probably in milli seconds
@Override
public int read (org.omg.ETF.BufferHolder data,
int offset,
int min_length,
int max_length,
long time_out)
{
SocketChannel myChannel;
synchronized (this)
{
myChannel = channel;
}
if (!myChannel.isConnected())
{
throw new org.omg.CORBA.COMM_FAILURE ("read() did not return any data");
}
long nanoDeadline = (time_out == 0 ? Long.MAX_VALUE : System.nanoTime() + time_out * 1000000);
ReadCallback callback = new ReadCallback (data, offset, min_length, max_length);
SelectorRequest request = new SelectorRequest (SelectorRequest.Type.READ, myChannel,
callback, nanoDeadline);
if (!selectorManager.add (request))
{
if (request.status == SelectorRequest.Status.EXPIRED)
{
throw new TIMEOUT("Message expired before write attempt.");
}
else
{
throw handleCommFailure(new IOException("Unable to add read request to SelectorManager"));
}
}
request.waitOnCompletion (nanoDeadline);
if (request.status == SelectorRequest.Status.EXPIRED || !request.isFinalized())
{
throw new TIMEOUT("Message expired before write attempt.");
}
else if (request.status == SelectorRequest.Status.FAILED)
{
throw new org.omg.CORBA.COMM_FAILURE ("Read request failed. Request status: FAILED");
}
else if (request.status == SelectorRequest.Status.SHUTDOWN)
{
throw new org.omg.CORBA.TRANSIENT ("Read request failed. Request status: SHUTDOWN");
}
return callback.readLength;
}
// time_out is probably in milli seconds
@Override
public void write (boolean is_first,
boolean is_last,
byte[] data,
int offset,
int length,
long time_out)
{
SocketChannel myChannel;
synchronized (this)
{
myChannel = channel;
}
if (!myChannel.isConnected())
{
throw handleCommFailure(new IOException("Channel has been closed"));
}
long nanoDeadline = (time_out == 0 ? Long.MAX_VALUE : System.nanoTime() + time_out * 1000000);
WriteCallback writeCallback = new WriteCallback (data, offset, length);
SelectorRequest request = new SelectorRequest (SelectorRequest.Type.WRITE, myChannel,
writeCallback, nanoDeadline);
if (!selectorManager.add (request))
{
if (request.status == SelectorRequest.Status.EXPIRED)
{
throw new TIMEOUT("Message expired before write attempt.");
}
else
{
throw handleCommFailure(new IOException("Unable to add write request to SelectorManager"));
}
}
request.waitOnCompletion (nanoDeadline);
if (!writeCallback.writeFinished())
{
int failCount = 0;
synchronized (this)
{
failCount = ++failedWriteAttempts;
}
if (failCount >= maxConsecutiveTimeouts)
{
boolean isConnected;
isConnected = myChannel.isConnected();
try
{
myChannel.close();
}
catch (IOException ex)
{
// disregard
logger.debug ("Exception while trying to close channel after write failure. "
+ ex.getMessage());
}
if (isDebugEnabled)
{
logger.debug ("Write attempts exceeded maximum allowed attempts (" + maxConsecutiveTimeouts +
"). " + (isConnected ? "Closing channel." : "Channel already closed."));
}
return;
}
throw new TIMEOUT("Message expired before write attempt.");
}
else
{
synchronized (this)
{
failedWriteAttempts = 0;
}
}
}
public void flush()
{
// no op
}
private class ReadCallback extends SelectorRequestCallback
{
private final ByteBuffer byteBuffer;
private final org.omg.ETF.BufferHolder data;
private final int offset;
private final int min_length;
public int readLength = 0;
public ReadCallback (org.omg.ETF.BufferHolder data, int offset, int min_length, int max_length)
{
super ();
byteBuffer = ByteBuffer.allocate (max_length);
byteBuffer.clear ();
this.data = data;
this.offset = offset;
this.min_length = min_length;
}
public boolean call (SelectorRequest request)
{
SocketChannel myChannel = request.channel;
try
{
if (request.status == SelectorRequest.Status.READY)
{
int numRead = myChannel.read (byteBuffer);
if (numRead < 0)
{
// Remote entity shut the socket down cleanly. Do the
// same from our end and cancel the channel.
myChannel.close();
if (isDebugEnabled)
{
logger.debug("Transport to " + connection_info +
": stream closed on read < 0" );
}
}
else
{
if (byteBuffer.position() < min_length)
{
// need more data, reactivate channel by returning true
return true;
}
else
{
readLength = byteBuffer.position();
byteBuffer.rewind ();
byteBuffer.get (data.value, offset, readLength);
}
}
}
}
catch (IOException ex)
{
try
{
myChannel.close();
}
catch (IOException ex2)
{
logger.error ("Failed to close channel: " + ex2.toString());
}
if (isDebugEnabled)
{
logger.debug("Got IOException in read(). Transport to " + connection_info +
": stream closed: " + ex.toString());
}
}
return false;
}
}
private class WriteCallback extends SelectorRequestCallback
{
final ByteBuffer byteBuffer;
final int length;
private int writeCount = 0;
public synchronized boolean writeFinished ()
{
return writeCount == length;
}
public WriteCallback (byte[] data, int offset, int length)
{
super ();
this.length = length;
// allocate a bytebuffer with the input data size
byteBuffer = ByteBuffer.allocate (length);
byteBuffer.clear ();
// copy bytes into ByteBuffer
byteBuffer.put (data, offset, length);
byteBuffer.flip ();
}
public boolean call (SelectorRequest request)
{
SocketChannel myChannel = request.channel;
try
{
if (request.status == SelectorRequest.Status.READY)
{
int bytesWritten = myChannel.write (byteBuffer);
synchronized (this)
{
writeCount += bytesWritten;
}
if (isDebugEnabled)
{
logger.debug ("wrote {} bytes to {}", bytesWritten, connection_info);
}
// if buffer isn't empty request to be reactivated
if (byteBuffer.hasRemaining())
{
return true;
}
}
}
catch (IOException ex)
{
try
{
myChannel.close();
}
catch (IOException ex2)
{
logger.error ("Failed to close channel: " + ex2.toString());
}
if (isDebugEnabled)
{
logger.debug("Got IOException in write(). Transport to " + connection_info +
": stream closed: " + ex.toString());
}
}
return false;
}
}
}