net.timewalker.ffmq4.listeners.tcp.io.TcpListener.java Source code

Java tutorial

Introduction

Here is the source code for net.timewalker.ffmq4.listeners.tcp.io.TcpListener.java

Source

/*
 * This file is part of FFMQ.
 *
 * FFMQ is free software; you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * FFMQ is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License for more details.
 * 
 * You should have received a copy of the GNU Lesser General Public License
 * along with FFMQ; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 */
package net.timewalker.ffmq4.listeners.tcp.io;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.InetAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.security.KeyStore;

import javax.jms.JMSException;
import javax.net.ssl.KeyManager;
import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLServerSocket;

import net.timewalker.ffmq4.FFMQCoreSettings;
import net.timewalker.ffmq4.FFMQException;
import net.timewalker.ffmq4.FFMQServerSettings;
import net.timewalker.ffmq4.jmx.JMXAgent;
import net.timewalker.ffmq4.listeners.ClientProcessor;
import net.timewalker.ffmq4.listeners.tcp.AbstractTcpClientListener;
import net.timewalker.ffmq4.local.FFMQEngine;
import net.timewalker.ffmq4.transport.PacketTransport;
import net.timewalker.ffmq4.transport.PacketTransportException;
import net.timewalker.ffmq4.transport.PacketTransportType;
import net.timewalker.ffmq4.transport.tcp.io.TcpPacketTransport;
import net.timewalker.ffmq4.utils.Settings;
import net.timewalker.ffmq4.utils.id.UUIDProvider;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

/**
 * TcpNetworkServer
 */
public final class TcpListener extends AbstractTcpClientListener implements Runnable, TcpListenerMBean {
    private static final Log log = LogFactory.getLog(TcpListener.class);

    // Runtime
    private ServerSocket serverSocket;
    private Thread listenerThread;
    private boolean stopRequired = false;
    private boolean usingSSL;

    /**
     * Constructor
     */
    public TcpListener(FFMQEngine engine, String listenAddr, int port, Settings settings) {
        this(engine, listenAddr, port, settings, null);
    }

    /**
     * Constructor
     */
    public TcpListener(FFMQEngine engine, String listenAddr, int port, Settings settings, JMXAgent jmxAgent) {
        super(engine, settings, jmxAgent, listenAddr, port);
        this.usingSSL = settings.getBooleanProperty(FFMQCoreSettings.TRANSPORT_TCP_SSL_ENABLED, false);
    }

    /* (non-Javadoc)
     * @see net.timewalker.ffmq4.listeners.AbstractListener#start()
     */
    @Override
    public synchronized void start() throws JMSException {
        if (started)
            return;

        log.info("Starting listener [" + getName() + "]");

        stopRequired = false;

        initServerSocket();
        listenerThread = new Thread(this, "FFMQ-TCP-Server-" + serverSocket.getLocalPort());
        listenerThread.start();

        started = true;
    }

    /* (non-Javadoc)
     * @see net.timewalker.ffmq4.listeners.Listener#getName()
     */
    @Override
    public String getName() {
        return (usingSSL ? PacketTransportType.TCPS : PacketTransportType.TCP) + "-" + listenAddr + "-"
                + listenPort;
    }

    private void initServerSocket() throws JMSException {
        try {
            InetAddress bindAddress = getBindAddress();
            int tcpBackLog = settings.getIntProperty(FFMQServerSettings.LISTENER_TCP_BACK_LOG,
                    DEFAULT_TCP_BACK_LOG);
            log.debug("TCP back log = " + tcpBackLog);

            serverSocket = createServerSocket(listenPort, tcpBackLog, bindAddress, usingSSL);
            serverSocket.setReuseAddress(true);
        } catch (JMSException e) {
            throw e;
        } catch (Exception e) {
            throw new FFMQException("Could not initialize server socket", "NETWORK_ERROR", e);
        }
    }

    private void closeServerSocket() {
        // Close the listen socket
        try {
            if (serverSocket != null)
                serverSocket.close();
        } catch (IOException e) {
            log.error("Could not close server socket", e);
        } finally {
            serverSocket = null;
        }
    }

    /*
     * (non-Javadoc)
     * @see net.timewalker.ffmq4.utils.concurrent.SynchronizableThread#run()
     */
    @Override
    public void run() {
        try {
            log.debug("Waiting for clients [" + getName() + "]");
            while (!stopRequired) {
                Socket clientSocket = serverSocket.accept();

                // Enforce listener capacity
                int activeClients = getActiveClients();
                if (activeClients >= listenerCapacity) {
                    log.warn("Listener is full (max=" + listenerCapacity + "), dropping new connection attempt.");
                    try {
                        clientSocket.close();
                    } catch (Exception e) {
                        log.error("Cannot close incoming connection", e);
                    }
                    continue;
                }

                String clientId = UUIDProvider.getInstance().getShortUUID();
                log.debug("Accepting a new client from " + clientSocket.getInetAddress().getHostAddress() + " ("
                        + (activeClients + 1) + ") : " + clientId + " [" + getName() + "]");
                try {
                    ClientProcessor processor = createProcessor(clientId, clientSocket);
                    registerClient(processor);
                    processor.start();
                } catch (Exception e) {
                    try {
                        clientSocket.close();
                    } catch (Exception ex) {
                        log.error("Could not close socket [" + getName() + "]", ex);
                    }

                    log.error("Client failed : " + clientId + " [" + getName() + "]", e);
                }
            }
        } catch (Exception e) {
            if (!stopRequired)
                log.fatal("Server failed [" + getName() + "]", e);
        }
    }

    /**
     * Create a new processor
     */
    protected ClientProcessor createProcessor(String clientId, Socket clientSocket)
            throws PacketTransportException {
        PacketTransport transport = new TcpPacketTransport(clientId, clientSocket, settings);
        ClientProcessor clientProcessor = new ClientProcessor(clientId, this, localEngine, transport);
        return clientProcessor;
    }

    /* (non-Javadoc)
     * @see net.timewalker.ffmq4.listeners.AbstractListener#stop()
     */
    @Override
    public synchronized void stop() {
        if (!started)
            return;

        log.info("Stopping listener [" + getName() + "]");

        stopRequired = true;

        // Close the listen socket
        closeServerSocket();

        // Wait for listener thread to stop
        try {
            if (listenerThread != null)
                listenerThread.join();
        } catch (InterruptedException e) {
            log.error("Wait for listener thread termination was interrupted");
        } finally {
            listenerThread = null;
        }

        // Then stop remaining clients
        closeRemainingClients();

        started = false;
    }

    private ServerSocket createServerSocket(int port, int tcpBackLog, InetAddress localAddr, boolean useSSL)
            throws JMSException {
        try {
            if (useSSL) {
                SSLServerSocket socket = (SSLServerSocket) createSSLContext().getServerSocketFactory()
                        .createServerSocket(port, tcpBackLog, localAddr);
                socket.setNeedClientAuth(false);
                return socket;
            } else
                return new ServerSocket(port, tcpBackLog, localAddr);
        } catch (Exception e) {
            throw new FFMQException("Cannot create server socket", "NETWORK_ERROR", e);
        }
    }

    private SSLContext createSSLContext() throws JMSException {
        try {
            String sslProtocol = settings.getStringProperty(FFMQCoreSettings.TRANSPORT_TCP_SSL_PROTOCOL, "SSLv3");
            String keyManagerAlgorithm = settings
                    .getStringProperty(FFMQCoreSettings.TRANSPORT_TCP_SSL_KEYMANAGER_ALGORITHM, "SunX509");
            String keyStoreType = settings.getStringProperty(FFMQCoreSettings.TRANSPORT_TCP_SSL_KEYSTORE_TYPE,
                    "JKS");
            String keyStorePath = settings.getStringProperty(FFMQCoreSettings.TRANSPORT_TCP_SSL_KEYSTORE_PATH,
                    "../conf/server-keystore.jks");
            String keyStorePass = settings.getStringProperty(FFMQCoreSettings.TRANSPORT_TCP_SSL_KEYSTORE_PASWORD,
                    "ffmqpass");
            String keyPass = settings.getStringProperty(FFMQCoreSettings.TRANSPORT_TCP_SSL_KEYSTORE_KEY_PASSWORD,
                    "ffmqpass");

            SSLContext sslContext = SSLContext.getInstance(sslProtocol);
            log.debug("Created an SSL context : protocol=[" + sslContext.getProtocol() + "] provider=["
                    + sslContext.getProvider() + "]");

            // Load available keys
            KeyManager[] keyManagers;
            File keyStoreFile = new File(keyStorePath);
            if (!keyStoreFile.canRead())
                throw new FFMQException("Cannot read keystore file : " + keyStoreFile.getAbsolutePath(),
                        "FS_ERROR");

            KeyStore ks = KeyStore.getInstance(keyStoreType);
            log.debug("Created keystore : type=[" + ks.getType() + "] provider=[" + ks.getProvider() + "]");
            char ksPass[] = keyStorePass.toCharArray();
            char ctPass[] = keyPass.toCharArray();
            log.debug("Loading keystore from " + keyStoreFile.getAbsolutePath());
            InputStream kis = new FileInputStream(keyStoreFile);
            ks.load(kis, ksPass);
            kis.close();

            KeyManagerFactory kmf = KeyManagerFactory.getInstance(keyManagerAlgorithm);
            log.debug("Created KeyManagerFactory : algorithm=[" + kmf.getAlgorithm() + "] provider=["
                    + kmf.getProvider() + "]");
            log.debug("Initializing KeyManagerFactory with keystore ...");
            kmf.init(ks, ctPass);

            keyManagers = kmf.getKeyManagers();

            sslContext.init(keyManagers, null, null);

            return sslContext;
        } catch (JMSException e) {
            throw e;
        } catch (Exception e) {
            throw new FFMQException("Cannot create SSL context", "NETWORK_ERROR", e);
        }
    }
}