/*********************************************************************
* ConnectionWriter.java
* created on 05.03.2005 by netseeker
* $Source: /cvsroot/ejoe/EJOE/src/de/netseeker/ejoe/core/ConnectionWriter.java,v $
* $Date: 2007/03/25 15:03:20 $
* $Revision: 1.4 $
*
* ====================================================================
*
* 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.IOException;
import java.io.OutputStream;
import java.net.SocketTimeoutException;
import java.nio.ByteBuffer;
import java.nio.channels.Channels;
import java.nio.channels.ClosedChannelException;
import java.nio.channels.SelectionKey;
import java.nio.channels.WritableByteChannel;
import java.rmi.RemoteException;
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.io.ByteBufferOutputStream;
import de.netseeker.ejoe.io.DataChannel;
import de.netseeker.ejoe.io.IOUtil;
import de.netseeker.ejoe.io.IncompleteIOException;
/**
* ConnectionWriter serializes a server answer and sends it through the established connection.
*
* @author netseeker
* @since 0.3.1
*/
final class ConnectionWriter implements Runnable
{
private static final Logger log = Logger.getLogger( ConnectionReader.class.getName() );
private final ChannelRegistrar _registrar;
private final ConnectionHeader _senderInfo;
private ConnectionHeader _receiverInfo;
/**
* Creates a new instance of ConnectionWriter
*
* @param serverInfo
* @param clientInfo
* @param channel
* @param adapter
* @param attachment
*/
public ConnectionWriter(final ChannelRegistrar registrar, final ServerInfo senderInfo, ConnectionHeader receiverInfo)
{
this._senderInfo = senderInfo;
this._receiverInfo = receiverInfo;
this._registrar = registrar;
}
/*
* (non-Javadoc)
*
* @see java.lang.Runnable#run()
*/
public void run()
{
try
{
if ( this._senderInfo.hasNonBlockingReadWrite() )
{
write();
}
else
{
writeBlocked();
}
this._receiverInfo.releaseWaitingBuffer();
this._receiverInfo.releaseAttachment();
if ( !this._senderInfo.isPersistent() || !this._receiverInfo.isPersistent() )
{
log.log( Level.FINEST, "Non-persistent connection detected, closing connection..." );
shutdownConnection();
}
else if ( this._registrar.isValid() )
{
log.log( Level.FINEST,
"Persistent connection detected, registering connections for further read events..." );
this._registrar.register( this._receiverInfo, SelectionKey.OP_READ );
}
}
catch ( ClosedChannelException cce )
{
log.log( Level.INFO, "Channel closed by client.", cce );
shutdownConnection();
}
catch ( IncompleteIOException ioe )
{
if ( this._registrar.isValid() )
{
this._receiverInfo.setWaitingBuffer( ioe.getIOBuffer() );
this._registrar.register( this._receiverInfo, SelectionKey.OP_WRITE );
}
}
catch ( SocketTimeoutException ste )
{
log.log( Level.FINE, "Timeout occured while sending data!", ste );
shutdownConnection();
}
catch ( RemoteException re )
{
this._receiverInfo.releaseWaitingBuffer();
this._receiverInfo.setAttachment( re );
this._registrar.register( this._receiverInfo, SelectionKey.OP_WRITE );
}
catch ( IOException e )
{
log.log( Level.WARNING,
"!!! IOException while sending data to client propably the client just closed the connection!!!",
e );
shutdownConnection();
}
catch ( Throwable e )
{
// something goes completely wrong!
log.log( Level.SEVERE, "!!! Unknown exception while sending data !!!", e );
shutdownConnection();
}
}
private void shutdownConnection()
{
IOUtil.closeQuiet( this._receiverInfo.getChannel() );
this._receiverInfo = null;
}
/**
* @throws IOException
*/
private void write() throws IOException
{
ByteBufferOutputStream out = null;
ByteBuffer dataBuf = null;
SerializeAdapter adapter = this._receiverInfo.getAdapterName() != null ? AdapterFactory
.createAdapter( this._receiverInfo.getAdapterName() ) : null;
WritableByteChannel channel = this._receiverInfo.getChannel();
DataChannel dataChannel = DataChannel.getInstance( this._receiverInfo );
try
{
if ( this._receiverInfo.hasAttachment() )
{
// usual way: convert the serialized object to a ByteBuffer
if ( !this._receiverInfo.isDirect() )
{
out = new ByteBufferOutputStream();
serialize( adapter, out );
dataBuf = out.getBackingBuffer();
}
// direct mode: just use the attachement
else
{
ByteBuffer tmp = (ByteBuffer) this._receiverInfo.getAttachment();
if ( tmp.position() > 0 )
{
tmp.flip();
}
dataBuf = tmp.duplicate();
ByteBufferAllocator.collect( tmp );
}
if ( dataBuf.position() > 0 )
{
dataBuf.flip();
}
this._receiverInfo.releaseAttachment();
dataChannel.writeHeader( this._receiverInfo, dataBuf, EJConstants.EJOE_CONNECTION_TIMEOUT );
if ( log.isLoggable( Level.FINE ) )
{
byte[] tmp = new byte[dataBuf.remaining()];
dataBuf.get( tmp );
dataBuf.position( 0 );
log.log( Level.FINE, "Going to send server response:\n"
+ new String( tmp, EJConstants.EJOE_DEFAULT_CHARSET ) );
}
dataChannel.nonBlockingWrite( channel, dataBuf );
}
else if ( this._receiverInfo.hasWaitingBuffer() )
{
dataBuf = this._receiverInfo.getWaitingBuffer();
dataChannel.nonBlockingWrite( channel, dataBuf );
}
// seems that we have no answer for the client
else
{
dataChannel.writeHeader( this._receiverInfo, null, EJConstants.EJOE_CONNECTION_TIMEOUT );
}
log.log( Level.FINE, "Server response sent." );
}
finally
{
IOUtil.closeQuiet( out );
}
}
/**
* @throws IOException
*/
private void writeBlocked() throws IOException
{
SerializeAdapter adapter = this._receiverInfo.getAdapterName() != null ? AdapterFactory
.createAdapter( this._receiverInfo.getAdapterName() ) : null;
log.log( Level.FINE, "Going to send server response... " );
if ( !this._receiverInfo.isDirect() )
{
serialize( adapter, Channels.newOutputStream( this._receiverInfo.getChannel() ) );
}
// direct mode: just use the attachement
else
{
ByteBuffer tmp = (ByteBuffer) this._receiverInfo.getAttachment();
if ( tmp.position() > 0 )
{
tmp.flip();
}
IOUtil.writeDirect( Channels.newOutputStream( this._receiverInfo.getChannel() ), tmp );
}
log.log( Level.FINE, "Server response sent." );
}
/**
* @param out
* @throws IOException
*/
private void serialize( SerializeAdapter adapter, OutputStream out ) throws IOException
{
boolean compressed = this._senderInfo.hasCompression() && this._receiverInfo.hasCompression();
try
{
IOUtil.adapterSerialize( adapter, out, this._receiverInfo.getAttachment(), compressed, this._senderInfo
.getCompressionLevel() );
}
catch ( IOException ioe )
{
throw ioe;
}
catch ( Throwable t )
{
throw new RemoteException( "The server encounteres an error while serializing the server response!", t );
}
}
}