All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.crankuptheamps.client.TCPTransport Maven / Gradle / Ivy

////////////////////////////////////////////////////////////////////////////
//
// Copyright (c) 2010-2024 60East Technologies Inc., All Rights Reserved.
//
// This computer software is owned by 60East Technologies Inc. and is
// protected by U.S. copyright laws and other laws and by international
// treaties.  This computer software is furnished by 60East Technologies
// Inc. pursuant to a written license agreement and may be used, copied,
// transmitted, and stored only in accordance with the terms of such
// license agreement and with the inclusion of the above copyright notice.
// This computer software or any other copies thereof may not be provided
// or otherwise made available to any other person.
//
// U.S. Government Restricted Rights.  This computer software: (a) was
// developed at private expense and is in all respects the proprietary
// information of 60East Technologies Inc.; (b) was not developed with
// government funds; (c) is a trade secret of 60East Technologies Inc.
// for all purposes of the Freedom of Information Act; and (d) is a
// commercial item and thus, pursuant to Section 12.212 of the Federal
// Acquisition Regulations (FAR) and DFAR Supplement Section 227.7202,
// Government's use, duplication or disclosure of the computer software
// is subject to the restrictions set forth by 60East Technologies Inc..
//
////////////////////////////////////////////////////////////////////////////

package com.crankuptheamps.client;

import java.net.Socket;
import java.net.URI;
import java.nio.ByteBuffer;
import java.beans.ExceptionListener;
import java.util.Properties;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

import com.crankuptheamps.client.Message.SerializationResult;
import com.crankuptheamps.client.exception.AlreadyConnectedException;
import com.crankuptheamps.client.exception.ConnectionRefusedException;
import com.crankuptheamps.client.exception.DisconnectedException;
import com.crankuptheamps.client.exception.InvalidURIException;
import com.crankuptheamps.client.exception.RetryOperationException;

/**
 * Implements an AMPS transport over standard TCP/IP. 
 * Use this transport by connecting to URIs beginning with {@code tcp://}. 
 */

public class TCPTransport implements Transport
{
    protected TCPTransportImpl _impl                  = null;
    private Protocol           _protocol              = null;
    private final Lock         _sendLock              = new ReentrantLock();
    private ByteBuffer         _sendBuffer            = ByteBuffer.allocate(4096);
    private static int         _defaultReadTimeout    = 0;
    private static int         _defaultConnectTimeout = 0;

    // Other clients use daemon threads by default, but
    // we have customer that depends on non-daemon threads (i.e. they
    // want the program to continue running even once main() exits.)
    private static boolean _useDaemonThreads       = false;

    /**
     * Constructs a new TCPTransport instance with the specified protocol and properties.
     * @param protocol   The protocol used by the transport.
     * @param properties The properties to configure the transport.
     * @return A new TCPTransportImpl instance.
     */
    protected TCPTransportImpl constructTransportImpl(Protocol protocol, Properties properties)
    {
        return new TCPTransportImpl(protocol, properties, new DefaultTransportFilter());
    }

    /**
     * Constructs a new TCPTransport instance with the specified protocol and properties.
     * @param protocol    The protocol used by the transport.
     * @param properties  The properties to configure the transport.
     */
    public TCPTransport(Protocol protocol, Properties properties)
    {
        this._protocol = protocol;
        this._impl = constructTransportImpl(protocol,properties);
    }

    /**
     * Constructs a new TCPTransport instance with the specified message type.
     * @param msgType The protocol message type.
     */
    public TCPTransport(Protocol msgType)
    {
        this(msgType, new Properties());
    }

    public TCPTransport(Protocol msgType, TCPTransportImpl impl)
    {
        this(msgType, new Properties());
        this._impl = impl;
    }

    /**
     * Sets whether daemon threads should be used by default.
     * @param daemonThreads True to use daemon threads; false otherwise.
     */
    public static void setDaemon(boolean daemonThreads)
    {
        TCPTransport._useDaemonThreads = daemonThreads;
    }

    /**
     * Checks if daemon threads are being used by default.
     * @return True if daemon threads are used; false otherwise.
     */
    public static boolean isDaemon()
    {
        return TCPTransport._useDaemonThreads;
    }

    /**
     * Sets the default read timeout used for socket reads. This value is
     * used for new connections, but not applied to existing connections. A 0
     * value means to wait forever for incoming data, or until an error occurs.
     *
     * @param defaultReadTimeoutMillis_ The default read timeout in milliseconds.
     */
    public static void setDefaultReadTimeout(int defaultReadTimeoutMillis_)
    {
        TCPTransport._defaultReadTimeout = defaultReadTimeoutMillis_;
    }

    /**
     * Returns the default read timeout used for socket reads. A 0 value
     * means to wait forever for incoming data, or until an error occurs.
     *
     * @return The current default read timeout, in milliseconds.
     */
    public static int getDefaultReadTimeout()
    {
        return TCPTransport._defaultReadTimeout;
    }

    /**
     * Returns the default connect timeout used for new connections.
     *
     * @param defaultConnectTimeoutMillis_ The default connect timeout,
     * in milliseconds. A 0 value means no timeout is specified, i.e. wait
     * until the OS returns an error.
     */
    public static void setDefaultConnectTimeout(int defaultConnectTimeoutMillis_)
    {
        TCPTransport._defaultConnectTimeout = defaultConnectTimeoutMillis_;
    }

    /**
     * Returns the default connect timeout used for new connections.
     *
     * @return The default connect timeout in milliseconds. A 0 value means
     *       no timeout is specified, i.e. wait until the OS returns an error.
     */
    public static int getDefaultConnectTimeout()
    {
        return TCPTransport._defaultConnectTimeout;
    }

    /**
     * Factory method for creating a new instance of {@link TCPTransport}.
     * @param messageType The protocol message type for the transport.
     * @return A new instance of TCPTransport with the specified protocol message type.
     */
    public static TCPTransport createTransport(Protocol messageType)
    {
        return new TCPTransport(messageType);
    }

    /**
     * Factory method for creating a new instance of {@link TCPTransport}.
     * @param messageType The protocol message type for the transport.
     * @param impl The underlying implementation for the new transport.
     * @return A new instance of TCPTransport with the specified protocol message type.
     */
    public static TCPTransport createTransport(Protocol messageType, TCPTransportImpl impl)
    {
        return new TCPTransport(messageType, impl);
    }

    /**
     * Sets the message handler for this transport.
     * @param ml The message handler to set.
     */
    public void setMessageHandler(MessageHandler ml)
    {
        this._impl.setMessageHandler(ml);
    }

    /**
     * Sets the handler for newly created threads.
     * @param tch_ The thread created handler to set.
     */
    public void setThreadCreatedHandler(ThreadCreatedHandler tch_)
    {
        this._impl.setThreadCreatedHandler(tch_);
    }

    /**
     * Sets the handler for disconnect events.
     * @param dh The disconnect handler to set.
     */
    public void setDisconnectHandler(TransportDisconnectHandler dh)
    {
        this._impl.setDisconnectHandler(dh);
    }

    /**
     * Sets the listener for exception events.
     * @param exceptionListener The exception listener to set.
     */
    public void setExceptionListener(ExceptionListener exceptionListener)
    {
        this._impl.setExceptionListener(exceptionListener);
    }

    /**
     * Sets the transport filter.
     * @param filter The transport filter to set.
     */
    public void setTransportFilter(TransportFilter filter)
    {
        _impl.setTransportFilter(filter);
    }

    /**
     * Connects to the specified URI using the underlying implementation.
     * @param uri The URI to connect to.
     * @throws ConnectionRefusedException If the connection is refused.
     * @throws AlreadyConnectedException  If the transport is already connected.
     * @throws InvalidURIException       If the URI is invalid.
     */
    public void connect(URI uri) throws ConnectionRefusedException, AlreadyConnectedException, InvalidURIException
    {
        this._impl.connect(uri);
    }

    /**
     * Closes the transport, disconnecting it from the server.
     * @throws Exception If an error occurs during the disconnect process.
     */
    public void close() throws Exception
    {
        this._impl.disconnect();
    }

    /**
     * Disconnects the transport from the server.
     */
    public void disconnect()
    {
        this._impl.disconnect();
    }

    /**
     * Handles a close event, notifying the underlying implementation.
     * @param failedVersion_ The failed version.
     * @param message        The message associated with the close event.
     * @param e              The exception associated with the close event.
     * @throws DisconnectedException      If the transport is disconnected.
     * @throws RetryOperationException   If a retry operation is needed.
     */
    public void handleCloseEvent(int failedVersion_, String message, Exception e) throws DisconnectedException, RetryOperationException
    {
        this._impl.handleCloseEvent(failedVersion_, message, e);
    }

    /**
     * Sends a message without retrying, handling the serialization process.
     * @param message The message to send.
     * @throws DisconnectedException If the transport is disconnected.
     */
    public void sendWithoutRetry(Message message) throws DisconnectedException
    {
        this._sendLock.lock();
        try
        {
            _sendBuffer.clear();
            // Reserve space for a 4-byte int at the
            // beginning for the size of the message
            _sendBuffer.position(4);
            SerializationResult sr = message.serialize(_sendBuffer);
            if(sr == Message.SerializationResult.BufferTooSmall)
            {
                // Buffer was too small, let's resize and retry
                _sendBuffer = ByteBuffer.allocate(2 * _sendBuffer.capacity());
                sendWithoutRetry(message);
                return;
            }
            else if(sr == SerializationResult.OK)
            {
                // Write size integer now that we know the size
                _sendBuffer.putInt(0, _sendBuffer.position() - 4);
                // Flip buffer for reading
                _sendBuffer.flip();
                // Write buffer to socket
                this._impl.send(_sendBuffer);
            }
        }
        finally
        {
            this._sendLock.unlock();
        }
    }

    /**
     * Sends the specified message. This method handles synchronization, retries, and exception handling.
     * @param message The message to send.
     * @throws DisconnectedException If the transport is disconnected.
     */
    public void send(Message message) throws DisconnectedException
    {
        this._sendLock.lock();
        try
        {
            int currentVersion = getVersion();
            try
            {
                sendWithoutRetry(message);
            }
            catch(DisconnectedException ex)
            {
                try
                {
                    this.handleCloseEvent(currentVersion, "Exception occurred while sending", ex);
                }
                catch(RetryOperationException r)
                {
                    ; // retry
                }
                throw ex;
            }

        }
        finally
        {
            this._sendLock.unlock();
        }
    }

    /**
     * Allocates a new message using the underlying protocol.
     * @return The allocated message.
     */
    public Message allocateMessage()
    {
        return this._protocol.allocateMessage();
    }

    /**
     * Returns the size of the write queue.
     * @return The size of the write queue.
     * @throws DisconnectedException If the transport is disconnected.
     */
    public long writeQueueSize() throws DisconnectedException
    {
        try
        {
            return this._impl.writeQueueSize();
        }
        catch(NullPointerException e)
        {
            throw new DisconnectedException("not connected", e);
        }
    }

    /**
     * Returns the size of the read queue.
     * @return The size of the read queue.
     * @throws DisconnectedException If the transport is disconnected.
     */
    public long readQueueSize() throws DisconnectedException
    {
        try
        {
            return this._impl.writeQueueSize();
        }
        catch(NullPointerException e)
        {
            throw new DisconnectedException("not connected", e);
        }
    }

    /**
     * Flushes the transport's write queue.
     * @return The number of messages flushed.
     * @throws DisconnectedException If the transport is disconnected.
     */
    public long flush() throws DisconnectedException
    {
        try
        {
            return this._impl.flush();
        }
        catch(NullPointerException e)
        {
            throw new DisconnectedException("not connected", e);
        }
    }

    /**
     * Flushes the transport's write queue with a specified timeout.
     * @param timeout The maximum time to wait for flushing, in milliseconds.
     * @return The number of messages flushed.
     * @throws DisconnectedException If the transport is disconnected.
     */
    public long flush(long timeout) throws DisconnectedException
    {
        try
        {
            return this._impl.flush(timeout);
        }
        catch(NullPointerException e)
        {
            throw new DisconnectedException("not connected", e);
        }
    }

    /**
     * Returns the underlying socket of the transport.
     * @return The underlying socket.
     */
    public Socket socket()
    {
        try
        {
            return this._impl.socket();
        }
        catch(Exception e)
        {
            return null;
        }
    }

    /**
     * Returns the version of the transport.
     * @return The version of the transport.
     */
    public int getVersion()
    {
        return this._impl._connectionVersion;
    }

    /**
     * Sets the read timeout for the transport.
     * @param readTimeout_ The read timeout in milliseconds.
     */
    public void setReadTimeout(int readTimeout_)
    {
        this._impl.setReadTimeout(readTimeout_);
    }

    /**
     * Gets the transport's currently registered idle task, or null
     * if there is none.
     *
     * @return The currently registered idle runnable or null.
     */
    public AMPSRunnable getIdleRunnable() {
        return this._impl.getIdleRunnable();
    }

    /**
     * Sets the idle runnable for the transport.
     * @param runnable The idle runnable to set.
     */
    public void setIdleRunnable(AMPSRunnable runnable)
    {
        this._impl.setIdleRunnable(runnable);
    }

    public void initFromClient(Client client) {
        this._impl.initFromClient(client);
    }

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy