com.nokia.dempsy.messagetransport.tcp.TcpSender.java Source code

Java tutorial

Introduction

Here is the source code for com.nokia.dempsy.messagetransport.tcp.TcpSender.java

Source

/*
 * Copyright 2012 the original author or authors.
 *
 * 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.
 */

package com.nokia.dempsy.messagetransport.tcp;

import java.io.DataOutputStream;
import java.io.IOException;
import java.net.InetAddress;
import java.net.Socket;
import java.util.Arrays;

import org.apache.commons.io.IOUtils;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.nokia.dempsy.messagetransport.MessageTransportException;
import com.nokia.dempsy.messagetransport.Sender;

public class TcpSender implements Sender {
    private static Logger logger = LoggerFactory.getLogger(TcpSender.class);

    private TcpDestination destination;
    private Socket socket = null;
    private DataOutputStream dataOutputStream = null;

    private enum IsLocalAddress {
        Yes, No, Unknown
    };

    private IsLocalAddress isLocalAddress = IsLocalAddress.Unknown;

    protected TcpSender(TcpDestination destination) throws MessageTransportException {
        this.destination = destination;
    }

    @Override
    public synchronized void send(byte[] messageBytes) throws MessageTransportException {
        try {
            DataOutputStream localDataOutputStream = getDataOutputStream();

            localDataOutputStream.writeInt(messageBytes.length);
            localDataOutputStream.flush();
            localDataOutputStream.write(messageBytes);
            localDataOutputStream.flush();
        } catch (IOException ioe) {
            close();
            throw new MessageTransportException(
                    "It appears the client " + destination + " is no longer taking calls.", ioe);
        }
    }

    /**
     * This method is here for testing. It allows me to create a fake output stream that 
     * I can disrupt to test the behavior of network failures.
     */
    protected Socket makeSocket(TcpDestination destination) throws IOException {
        return new Socket(destination.inetAddress, destination.port);
    }

    // this shouldn't be called without holding a lock
    private DataOutputStream getDataOutputStream() throws MessageTransportException, IOException {
        if (dataOutputStream == null) // socket must also be null.
        {
            socket = makeSocket(destination);

            // There is a really odd circumstance (at least on Linux) where a connection 
            //  to a port in the dynamic range, while there is no listener on that port,
            //  from the same system/network interface, can result in a local port selection
            //  that's the same as the port that the connection attempt is to. In this case,
            //  for some reason the Socket instantiation (and connection) succeeds without
            //  a listener. We need to force a failure if this is the case.
            if (isLocalAddress == IsLocalAddress.Unknown) {
                if (socket.isBound()) {
                    InetAddress localSocketAddress = socket.getLocalAddress();
                    isLocalAddress = (Arrays.equals(localSocketAddress.getAddress(),
                            destination.inetAddress.getAddress())) ? IsLocalAddress.Yes : IsLocalAddress.No;
                }
            }

            if (isLocalAddress == IsLocalAddress.Yes) {
                if (socket.getLocalPort() == destination.port)
                    throw new IOException("Connection to self same port!!!");
            }

            dataOutputStream = new DataOutputStream(socket.getOutputStream());
        }

        return dataOutputStream;
    }

    // this shouldn't be called without holding a lock
    protected void close() {
        if (dataOutputStream != null)
            IOUtils.closeQuietly(dataOutputStream);
        dataOutputStream = null;

        closeQuietly(socket);
        socket = null;
    }

    protected void closeQuietly(Socket socket) {
        if (socket != null) {
            try {
                socket.close();
            } catch (IOException ioe) {
                if (logger.isDebugEnabled())
                    logger.debug("close socket failed for " + destination);
            }
        }
    }
}