/*********************************************************************
* ConnectionReader.java
* created on 05.03.2005 by netseeker
* $Source: /cvsroot/ejoe/EJOE/src/de/netseeker/ejoe/core/ConnectionReader.java,v $
* $Date: 2007/11/17 10:57:03 $
* $Revision: 1.7 $
*
* ====================================================================
*
* Copyright 2005-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 ejoe framework.
* For more information on the author, please see
* <http://www.manskes.de/>.
*
*********************************************************************/
package de.netseeker.ejoe.core;
import java.io.EOFException;
import java.io.IOException;
import java.io.InputStream;
import java.net.SocketTimeoutException;
import java.nio.ByteBuffer;
import java.nio.channels.Channels;
import java.nio.channels.ClosedChannelException;
import java.nio.channels.NonReadableChannelException;
import java.nio.channels.SelectionKey;
import java.nio.channels.SocketChannel;
import java.rmi.RemoteException;
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.ServerInfo;
import de.netseeker.ejoe.adapter.AdapterFactory;
import de.netseeker.ejoe.adapter.SerializeAdapter;
import de.netseeker.ejoe.cache.ByteBufferAllocator;
import de.netseeker.ejoe.handler.ServerHandler;
import de.netseeker.ejoe.http.HttpResponse;
import de.netseeker.ejoe.io.ByteBufferInputStream;
import de.netseeker.ejoe.io.ChannelInputStream;
import de.netseeker.ejoe.io.DataChannel;
import de.netseeker.ejoe.io.IOUtil;
import de.netseeker.ejoe.io.IncompleteIOException;
/**
* ConnectionReader targets three jobs:
* <ul>
* <li>Read (partial) data from a established client connection</li>
* <li>Invoke the server handler to process the received data</li>
* <li>Hand over the response to the ConnectionProcessor for further processing (sending to the client)</li>
* </ul>
*
* @author netseeker
* @since 0.3.0
*/
public class ConnectionReader implements Runnable
{
private static final Logger log = Logger.getLogger( ConnectionReader.class.getName() );
private final ChannelRegistrar _registrar;
private ConnectionHeader _senderInfo;
private ServerInfo _receiverInfo;
/**
* Creates a new instance of ConnectionReader
*
* @param channel a socket channel ready for reading and writing
* @param adapter (de)serialize adapter
* @param handler the working horse handling the transported input object
*/
public ConnectionReader(final ChannelRegistrar registrar, ServerInfo receiverInfo, ConnectionHeader senderInfo)
{
this._registrar = registrar;
this._receiverInfo = receiverInfo;
this._senderInfo = senderInfo;
}
/*
* (non-Javadoc)
*
* @see java.lang.Runnable#run()
*/
public void run()
{
Object request = null;
SocketChannel channel = this._senderInfo.getChannel();
try
{
if ( !this._senderInfo.isConnected() )
{
log.log( Level.FINEST, "Handshaking client..." );
// handshake the client, get infos about compression...
this._senderInfo = DataChannel.getInstance().handshake( this._receiverInfo, channel,
EJConstants.EJOE_CONNECTION_TIMEOUT );
}
if ( this._senderInfo != null )
{
log.log( Level.FINEST, "Remote requested " + this._senderInfo.getAdapterName() );
SerializeAdapter adapter = AdapterFactory.createAdapter( this._senderInfo.getAdapterName() );
if ( this._receiverInfo.hasNonBlockingReadWrite() )
{
log.log( Level.FINEST, "Going to read client request on a non blocking socket..." );
request = read( adapter );
}
else
{
log.log( Level.FINEST, "Going to read client request on a blocking socket..." );
request = readBlocked( adapter );
}
this._senderInfo.releaseAttachment();
this._senderInfo.releaseWaitingBuffer();
log.log( Level.FINE, "Client request read." );
Object result = handleObject( request );
if ( this._registrar.isValid() )
{
this._senderInfo.setAttachment( result );
this._registrar.register( this._senderInfo, SelectionKey.OP_WRITE );
}
}
else
{
log.log( Level.WARNING, "Connection timeout reached while waiting for Handshake complete. "
+ "Closing connection." );
shutdownConnection( channel );
}
}
// partial read detected, registering for read again
catch ( IncompleteIOException ioe )
{
this._senderInfo.setWaitingBuffer( ioe.getIOBuffer() );
this._registrar.register( this._senderInfo, SelectionKey.OP_READ );
}
catch ( EOFException eof )
{
log.log( Level.FINEST, "EOF received while reading client data " + "- closing connection." );
shutdownConnection( channel );
}
catch ( ParseException pe )
{
log.log( Level.WARNING, "Unparseable connection header detected!", pe );
if ( !this._senderInfo.isHttp() )
{
this._senderInfo.setAttachment( new RemoteException( "Unparseable connection header!", pe ) );
}
else
{
this._senderInfo.setAttachment( new RemoteException( "Unparseable connection header!", pe ),
HttpResponse.HTTP_BAD_REQUEST );
}
this._registrar.register( this._senderInfo, SelectionKey.OP_WRITE );
}
catch ( SocketTimeoutException ste )
{
log.log( Level.FINE, "Timeout occured while waiting for client data!", ste );
shutdownConnection( channel );
}
catch ( RemoteException re )
{
if ( !this._senderInfo.isHttp() )
{
this._senderInfo.setAttachment( re );
}
else
{
this._senderInfo.setAttachment( re, HttpResponse.HTTP_INTERNAL_SERVER_ERROR );
}
this._registrar.register( this._senderInfo, SelectionKey.OP_WRITE );
}
// the client did something strange with the channel, probably the
// client is just too slow for
catch ( NonReadableChannelException e )
{
log.log( Level.INFO, "Connection probably closed by client." );
shutdownConnection( channel );
}
// the client did close the connection, or the connection was
// disconnected
catch ( ClosedChannelException cce )
{
log.log( Level.INFO, "Connection closed by client." );
shutdownConnection( channel );
}
catch ( Throwable e )
{
if ( !channel.isBlocking() && channel.isConnected() && channel.isOpen() )
{
// something goes completely wrong!
log.log( Level.WARNING, "!!! Exception while reading client data !!! "
+ "Probably the client just closed the connection but it could also be a serious failure.", e );
}
else
{
// seems like a follow-up exception due to disconnect
log.log( Level.INFO, "Connection propably closed by client.", e );
}
shutdownConnection( channel );
}
}
private void shutdownConnection( SocketChannel channel )
{
IOUtil.closeQuiet( channel );
this._senderInfo = null;
}
/**
* Reads and deserialize a request object from a socket channel in non-blocking mode
*
* @param adapter the deserialization adapter to use
* @return the deserialized request object
* @throws IOException
* @throws UnsupportedOperationException
*/
private Object read( SerializeAdapter adapter ) throws IOException
{
ByteBufferInputStream in = null;
ByteBuffer dataBuf = null;
Object result = null;
DataChannel dataChannel = DataChannel.getInstance( this._senderInfo );
try
{
if ( !this._senderInfo.hasWaitingBuffer() )
{
int length = dataChannel.readHeader( this._senderInfo, EJConstants.EJOE_CONNECTION_TIMEOUT );
// maybe the DataChannel signals that it has already read
// partial data
if ( this._senderInfo.hasWaitingBuffer() )
{
dataBuf = this._senderInfo.getWaitingBuffer();
}
else
{
dataBuf = ByteBufferAllocator.allocate( length );
}
log.log( Level.FINE, "Going to read client request with length: " + length );
}
else
{
dataBuf = this._senderInfo.getWaitingBuffer();
}
if ( dataBuf.hasRemaining() )
{
DataChannel.nonBlockingRead( this._senderInfo.getChannel(), dataBuf );
}
dataBuf.flip();
if ( log.isLoggable( Level.FINE ) )
{
byte[] tmp = new byte[dataBuf.remaining()];
dataBuf.get( tmp );
dataBuf.position( 0 );
log.log( Level.FINE, "Client request read:\n" + new String( tmp, EJConstants.EJOE_DEFAULT_CHARSET ) );
}
if ( dataBuf.hasRemaining() )
{
try
{
// usual way: deserialize using a adapter
if ( !this._senderInfo.isDirect() )
{
dataBuf = dataChannel.decode( dataBuf );
in = new ByteBufferInputStream( dataBuf );
result = deserialize( adapter, in );
}
// direct mode: don't deserialize, just copy and return the read
// ByteBuffer
else
{
// copy the bytebuffer
ByteBuffer tmp = ByteBufferAllocator.allocate( dataBuf.remaining() );
tmp.put( dataBuf );
tmp.flip();
result = dataChannel.decode( tmp );
}
}
catch ( Throwable t )
{
throw new RemoteException( "Error while preprocessing request!", t );
}
finally
{
this._senderInfo.releaseWaitingBuffer();
}
}
}
finally
{
IOUtil.closeQuiet( in );
}
return result;
}
/**
* @return the _receiverInfo
*/
public ServerInfo getReceiverInfo()
{
return _receiverInfo;
}
/**
* @return the _senderInfo
*/
public ConnectionHeader getSenderInfo()
{
return _senderInfo;
}
/**
* @return the _registrar
*/
public ChannelRegistrar getRegistrar()
{
return _registrar;
}
/**
* Reads and deserialize a request object from a socket channel in blocking mode
*
* @return
* @throws IOException
* @throws UnsupportedOperationException
*/
private Object readBlocked( SerializeAdapter adapter ) throws Exception
{
// usual way: deserialize using a adapter
if ( !this._senderInfo.isDirect() )
{
return deserialize( adapter, new ChannelInputStream( Channels
.newInputStream( this._senderInfo.getChannel() ) ) );
}
// direct mode: don't deserialize, just copy and return the read
// ByteBuffer
else
{
return IOUtil
.readDirect( new ChannelInputStream( Channels.newInputStream( this._senderInfo.getChannel() ) ) );
}
}
/**
* @param clientInfo
* @param in
* @return
* @throws IOException
*/
protected Object deserialize( SerializeAdapter adapter, InputStream in ) throws Exception
{
boolean compressed = !this._senderInfo.isHttp() && this._receiverInfo.hasCompression()
&& this._senderInfo.hasCompression();
return IOUtil.adapterDeserialize( adapter, in, compressed );
}
/**
* @param obj
* @return
*/
protected Object handleObject( Object obj ) throws RemoteException
{
Object result = null;
ServerHandler handler = this._receiverInfo.getHandler();
try
{
result = handler.handle( obj );
}
catch ( Throwable e )
{
log.log( Level.WARNING, "Exception in ServerHandler " + this._receiverInfo.getHandler() + " occured.", e );
throw new RemoteException( "Server failed to proceed your request!", e );
}
return result;
}
}