Package de.netseeker.ejoe.io

Source Code of de.netseeker.ejoe.io.DataChannel

/*********************************************************************
* DataChannel.java
* created on 10.02.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 ejoe framework.
* For more information on the author, please see
* <http://www.manskes.de/>.
*
*********************************************************************/
package de.netseeker.ejoe.io;

import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.SocketTimeoutException;
import java.nio.ByteBuffer;
import java.nio.channels.ClosedChannelException;
import java.nio.channels.ReadableByteChannel;
import java.nio.channels.SelectionKey;
import java.nio.channels.SocketChannel;
import java.nio.channels.WritableByteChannel;
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;

/**
* Utility class handling all socket oriented data IO on nio channels. DataChannels must be implemented as singletons to
* avoid creation of a new object for each socket IO operation. Otherwise heavy load could result in fast-growing memory
* consumption.
*
* @author netseeker
* @since 0.3.9.1
*/
public class DataChannel
{
    private static final Logger logger      = Logger.getLogger( DataChannel.class.getName() );

    private static DataChannel  dataChannel = new DataChannel();

    /**
     * Singleton with hidden constructor, only child classes are allowed to construct new instances
     */
    protected DataChannel()
    {
        super();
    }

    /**
     * Invoking this method has the same effect as invoking {@link DataChannel#getInstance(null)}
     *
     * @return a default instance of DataChannel
     */
    public static DataChannel getInstance()
    {
        return getInstance( null );
    }

    /**
     * Returns appropiate instance of DataChannel for the given connection header. If the header is null an instance of
     * this class will be returned.
     *
     * @param header a valid connection header or null
     * @return an instance of DataChannel
     */
    public static DataChannel getInstance( ConnectionHeader header )
    {
        if ( header == null )
        {
            return dataChannel;
        }
        else if ( header.isHttp() )
        {
            return HttpChannel.getInstance();
        }
        else
        {
            return DefaultChannel.getInstance();
        }
    }

    /**
     * Handshake for a socket channel. It's used as workaround for a know issue with java sockets: Sometimes only the
     * first byte will get transferred through a socket connection when reading from it first time. The other bytes will
     * follow not until the next read. This method sends/receives one Byte through the socket to "initialize" the socket
     * channel. So all following read/write operations don't have to handle that "1-Byte issue". The send/received Byte
     * is used also as connection header, it contains information about compression, nio usage, if the connection is a
     * persistent or non-persistent one...
     *
     * @param sendBeforeReceive if true we will try to send one byte then read one byte otherwise we will use the
     *            opposite way around.
     * @throws IOException
     */
    public ConnectionHeader handshake( final ConnectionHeader header, SocketChannel channel, long timeout )
            throws IOException, ParseException
    {
        boolean isHttp = header.isHttp();
        ConnectionHeader receiverHeader = null;
        ByteBuffer magicBuf = null;
        byte[] preReadData = null;

        if ( !header.isClient() )
        {
            // expect to read the first four bytes for determining the used protocol
            magicBuf = ByteBufferAllocator.allocate( 4, false );
            semiBlockingRead( channel, magicBuf, timeout );
            // at least the handshake must be read in one operation
            // if not prevent us from dealing with bad networks or crappy clients
            if ( magicBuf.hasRemaining() ) return null;
            magicBuf.flip();
            // copy the read four byte into a buffer - we will need them again maybe
            preReadData = new byte[4];
            magicBuf.get( preReadData );
            magicBuf.rewind();
            // read the first four bytes as int
            int magicNo = magicBuf.getInt();
            isHttp = (magicNo != EJConstants.EJOE_MAGIC_NUMBER);
        }

        try
        {
            // seems like the usual EJOE protocol
            if ( !isHttp )
            {
                // complete the handshake using our DefaultChannel, we can skip the preread four bytes
                receiverHeader = DefaultChannel.getInstance().handshake( header, channel, timeout );
            }
            // seems like HTTP protocol
            else
            {
                // complete the handshake using a HttpChannel and hand over the preread four bytes
                receiverHeader = ((HttpChannel) HttpChannel.getInstance()).handshake( header, channel, preReadData,
                                                                                      timeout );
            }

            // copy the host into the received client header
            if ( receiverHeader.isClient() && receiverHeader.getHost() == null )
            {
                receiverHeader.setHost( header.getHost() );
            }
            // copy the adapter into the returned server header
            if ( header.isClient() && header.hasAdapter() && receiverHeader.getAdapterName() == null )
            {
                receiverHeader.setAdapterName( header.getAdapterName() );
            }
        }
        catch ( IncompleteIOException ioe )
        {
            // nothing to do
            // at least the handshake must be read in one operation
            // if not prevent us from dealing with bad networks or crappy clients
        }

        return receiverHeader;
    }

    /**
     * Tries to send the given ByteBuffer completely through the given SocketChannel three times
     *
     * @param channel
     * @param buffer
     * @throws IncompleteIOException if the given ByteBuffer could not be send completely
     * @throws IOException
     */
    public void nonBlockingWrite( WritableByteChannel channel, ByteBuffer buffer ) throws IOException
    {
        int runs = 0;

        do
        {
            channel.write( buffer );
            runs++;
        }
        while ( buffer.hasRemaining() && runs < EJConstants.NIO_MAX_ITERATIONS );

        if ( buffer.hasRemaining() )
        {
            logger.log( Level.FINEST, "Incomplete write detected, registering for write again." );
            buffer.compact();
            throw new IncompleteIOException( buffer, SelectionKey.OP_WRITE );
        }
    }

    /**
     * Tries to send the given ByteBuffer completely through the given SocketChannel within a given timeout
     *
     * @param channel
     * @param buffer
     * @param timeout
     * @throws IncompleteIOException if the given ByteBuffer could not be send completely
     * @throws IOException
     */
    public void semiBlockingWrite( WritableByteChannel channel, ByteBuffer buffer, long timeout ) throws IOException
    {
        long timestamp = System.currentTimeMillis();
        long timePeriod = -1;

        do
        {
            channel.write( buffer );
            timePeriod = System.currentTimeMillis() - timestamp;
        }
        while ( buffer.hasRemaining() && (timePeriod < timeout) );

        if ( timePeriod >= timeout )
        {
            throw new SocketTimeoutException();
        }

        if ( buffer.hasRemaining() )
        {
            logger.log( Level.FINEST, "Incomplete write detected, registering for write again." );
            buffer.compact();
            throw new IncompleteIOException( buffer, SelectionKey.OP_WRITE );
        }
    }

    /**
     * Tries to send the given ByteBuffer completely through the given SocketChannel within a given timeout
     *
     * @param channel
     * @param buffer
     * @return
     * @throws IOException
     */
    public static void nonBlockingRead( ReadableByteChannel channel, ByteBuffer buffer ) throws IOException
    {
        int read = 0, runs = 0;

        try
        {
            do
            {
                read = channel.read( buffer );
                runs++;
            }
            // read until end of data is reached (-1) or the buffer is full or we tried to read for
            // EJConstants.NIO_MAX_ITERATIONS
            while ( read != -1 && buffer.hasRemaining() && runs < EJConstants.NIO_MAX_ITERATIONS );
        }
        catch ( IOException e )
        {
            // most likely the sender did close the connection or something other (firewall?) does interfere the
            // communication
            ClosedChannelException che = new ClosedChannelException();
            che.setStackTrace( e.getStackTrace() );
            che.initCause( e.getCause() );
            throw che;
        }

        if ( buffer.hasRemaining() )
        {
            if ( read == -1 )
            {
                throw new ClosedChannelException();
            }
            else
            {
                logger.log( Level.FINEST, "Incomplete read detected, registering for read again." );
                throw new IncompleteIOException( buffer, SelectionKey.OP_READ );
            }
        }
    }

    /**
     * Tries to read ByteBuffer.remaining() bytes the into given ByteBuffer from the given SocketChannel within a given
     * timeout.
     *
     * @param channel
     * @param buffer
     * @param timeout
     * @return
     * @throws IOException
     */
    public static void semiBlockingRead( ReadableByteChannel channel, ByteBuffer buffer, long timeout )
            throws IOException
    {
        long timestamp = System.currentTimeMillis();
        long timePeriod = -1;
        int read = 0;

        try
        {
            do
            {
                read = channel.read( buffer );
                timePeriod = System.currentTimeMillis() - timestamp;
            }
            while ( read != -1 && buffer.hasRemaining() && (timePeriod < timeout) );
        }
        catch ( IOException e )
        {
            // most likely the sender did close the connection
            ClosedChannelException che = new ClosedChannelException();
            che.setStackTrace( e.getStackTrace() );
            che.initCause( e.getCause() );
            throw che;
        }

        if ( timePeriod >= timeout )
        {
            throw new SocketTimeoutException();
        }

        if ( buffer.hasRemaining() )
        {
            if ( read == -1 )
            {
                throw new ClosedChannelException();
            }
            else
            {
                logger.log( Level.FINEST, "Incomplete read detected, registering for read again." );
                throw new IncompleteIOException( buffer, SelectionKey.OP_READ );
            }
        }
    }

    /**
     * Receives a EJOE specific header containing the size of the next ByteBuffer.
     *
     * @param timeout read timeout
     * @return the length of the following data package
     * @throws IOException
     */
    public int readHeader( ConnectionHeader header, long timeout ) throws IOException
    {
        throw new UnsupportedOperationException();
    }

    /**
     * Sends a EJOE specific header containing the lengh of the given ByteBuffer
     *
     * @param timeout write timeout
     * @throws IOException
     */
    public void writeHeader( ConnectionHeader header, ByteBuffer buffer, long timeout ) throws IOException
    {
        throw new UnsupportedOperationException();
    }

    /**
     * Decodes and reformats request data if the underlying protocol layer makes it neccessary
     *
     * @param buffer
     */
    public ByteBuffer decode( ByteBuffer buffer ) throws UnsupportedEncodingException
    {
        // default implementation does nothing
        return buffer;
    }
}
TOP

Related Classes of de.netseeker.ejoe.io.DataChannel

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.