/*********************************************************************
* DefaultChannel.java
* created on 01.04.2006 by netseeker
* $Source$
* $Date$
* $Revision$
*
* ====================================================================
*
* Copyright 2006 netseeker aka Michael Manske
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* ====================================================================
*
* This file is part of the de.netseeker.ejoe.io framework.
* For more information on the author, please see
* <http://www.manskes.de/>.
*
*********************************************************************/
package de.netseeker.ejoe.io;
import java.io.IOException;
import java.net.InetAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.SocketChannel;
import java.text.ParseException;
import java.util.logging.Level;
import java.util.logging.Logger;
import de.netseeker.ejoe.ConnectionHeader;
import de.netseeker.ejoe.EJConstants;
import de.netseeker.ejoe.cache.ByteBufferAllocator;
/**
* @author netseeker
* @since 0.3.9.1
*/
class DefaultChannel extends DataChannel
{
private static final Logger logger = Logger.getLogger( DefaultChannel.class.getName() );
private static DefaultChannel dataChannel = new DefaultChannel();
private DefaultChannel()
{
super();
}
/**
* @return
*/
public static DataChannel getInstance()
{
return dataChannel;
}
/**
* @param header
* @param sendBeforeReceive
* @param timeout
* @return
* @throws IOException
*/
public ConnectionHeader handshake( final ConnectionHeader header, SocketChannel channel, long timeout )
throws IOException, ParseException
{
ConnectionHeader receiverHeader;
ByteBuffer magicBuf = null;
try
{
// shall we act as clientside and initialize the handshake?
if ( header.isClient() )
{
// ok, get the control bits as well as the adapter name in a
// ByteChannel
ByteBuffer tmpBuf = header.toByteBuffer();
magicBuf = ByteBufferAllocator.allocate( tmpBuf.remaining() + 4, false );
magicBuf.putInt( EJConstants.EJOE_MAGIC_NUMBER );
magicBuf.put( tmpBuf );
magicBuf.flip();
logger.log( Level.FINEST, "Sending Clientheader: " + header );
semiBlockingWrite( channel, magicBuf, timeout );
if ( magicBuf.hasRemaining() ) return null;
// the server will just answer with the header byte
magicBuf.clear();
magicBuf.limit( 1 );
}
else
{
// expect to read the header byte and an integer (the size of
// the forthcoming adapter name string)
// the magic integer was already read in #handshake
magicBuf = ByteBufferAllocator.allocate( 5, false );
}
// Server: read the client header byte + sizeof adapter name
// Client: read just the server header byte
semiBlockingRead( channel, magicBuf, timeout );
if ( magicBuf.hasRemaining() )
{
throw new ParseException( "Header too long!", magicBuf.position() );
}
magicBuf.flip();
InetAddress address = channel.socket().getInetAddress();
int port = channel.socket().getPort();
receiverHeader = new ConnectionHeader( channel, address.getHostAddress() + ':' + port, header.isClient(),
magicBuf.get() );
// shall we act as server side and answer to the handshake request?
if ( !header.isClient() )
{
if ( receiverHeader.hasAdapter() )
{
// ok, we have already read the client header byte, now read
// the adapter name string
if ( readAdapterName( receiverHeader, magicBuf, timeout ) == -1 ) return null;
}
// answer to the client request and send our serverside
// headerbyte
magicBuf.clear();
magicBuf.limit( 1 );
magicBuf.put( header.toByte() );
magicBuf.flip();
semiBlockingWrite( channel, magicBuf, timeout );
if ( magicBuf.hasRemaining() ) return null;
}
receiverHeader.setConnected( true );
}
finally
{
ByteBufferAllocator.collect( magicBuf );
}
return receiverHeader;
}
/**
* @param receiverHeader
* @param magicBuf
* @param timeout
* @return
* @throws IOException
*/
private int readAdapterName( ConnectionHeader receiverHeader, ByteBuffer magicBuf, long timeout )
throws IOException
{
int length = magicBuf.getInt();
if ( length > 1024 )
{
throw new IOException( "Invalid length for adapter name detected. The request is not well formatted!" );
}
// client requested a special adapter?
if ( length > 0 )
{
// limit the existing ByteBuffer if it's big enough
if ( length <= magicBuf.capacity() )
{
magicBuf.clear();
magicBuf.limit( length );
}
// otherwise allocate a new one
else
{
magicBuf = ByteBufferAllocator.allocate( length );
}
// read the adapter name
semiBlockingRead( receiverHeader.getChannel(), magicBuf, timeout );
if ( magicBuf.hasRemaining() ) return -1;
magicBuf.flip();
byte[] adapterArr = new byte[length];
magicBuf.get( adapterArr );
receiverHeader.setAdapterName( new String( adapterArr, EJConstants.EJOE_DEFAULT_CHARSET ) );
}
return length;
}
/*
* (non-Javadoc)
*
* @see de.netseeker.ejoe.io.DataChannel#readHeader(long)
*/
public int readHeader( ConnectionHeader header, long timeout ) throws IOException
{
ByteBuffer headerBuf = ByteBufferAllocator.allocate( EJConstants.NIO_HEADER_SIZE, false );
int read = 0;
SocketChannel channel = header.getChannel();
try
{
nonBlockingRead( channel, headerBuf );
headerBuf.flip();
read = headerBuf.getInt();
IOUtil.setReceiveBufferSize( channel.socket(), read );
}
catch ( IncompleteIOException ioe )
{
logger.log( Level.FINEST, "Incomplete header read detected, registering for read again." );
// ioe.setIOBuffer(null);
// ioe.setSelectionInterest(SelectionKey.OP_READ);
throw new IncompleteIOException( null, SelectionKey.OP_READ );
}
finally
{
ByteBufferAllocator.collect( headerBuf );
}
return read;
}
/*
* (non-Javadoc)
*
* @see de.netseeker.ejoe.io.DataChannel#writeHeader(de.netseeker.ejoe.ConnectionHeader, java.nio.ByteBuffer, long)
*/
public void writeHeader( ConnectionHeader header, ByteBuffer buffer, long timeout ) throws IOException
{
ByteBuffer headerBuf = ByteBufferAllocator.allocate( EJConstants.NIO_HEADER_SIZE, false );
SocketChannel channel = header.getChannel();
int length = buffer != null ? buffer.remaining() : 0;
headerBuf.putInt( length );
headerBuf.flip();
try
{
nonBlockingWrite( channel, headerBuf );
IOUtil.setSendBufferSize( channel.socket(), length );
}
catch ( IncompleteIOException ioe )
{
logger.log( Level.FINEST, "Incomplete header write detected, registering for write again." );
// ioe.setIOBuffer(null);
throw new IncompleteIOException( null, SelectionKey.OP_WRITE );
}
finally
{
ByteBufferAllocator.collect( headerBuf );
}
}
}