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

org.objectweb.dream.protocol.channel.SSLProtocolImpl Maven / Gradle / Ivy

/**
 * Dream
 * Copyright (C) 2003-2004 INRIA Rhone-Alpes
 *
 * This library 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.
 *
 * This library 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 this library; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 *
 * Contact: [email protected]
 *
 * Initial developer(s): Matthieu Leclercq
 * Contributor(s): Pierre GARCIA 
 */

package org.objectweb.dream.protocol.channel;

import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.net.InetAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.UnknownHostException;
import java.security.KeyManagementException;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.UnrecoverableKeyException;
import java.security.cert.CertificateException;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.TimeUnit;

import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLServerSocketFactory;
import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.TrustManagerFactory;

import org.objectweb.dream.IOPushException;
import org.objectweb.dream.PushException;
import org.objectweb.dream.control.activity.Util;
import org.objectweb.dream.control.activity.manager.TaskManager;
import org.objectweb.dream.control.activity.manager.ThreadPoolManager;
import org.objectweb.dream.control.activity.task.AbstractTask;
import org.objectweb.dream.control.activity.task.IllegalTaskException;
import org.objectweb.dream.control.activity.task.thread.ThreadPoolOverflowException;
import org.objectweb.dream.dreamannotation.DreamComponent;
import org.objectweb.dream.dreamannotation.DreamLifeCycle;
import org.objectweb.dream.dreamannotation.DreamMonolog;
import org.objectweb.dream.dreamannotation.util.DreamLifeCycleType;
import org.objectweb.dream.message.ChunkFactoryReference;
import org.objectweb.dream.message.Message;
import org.objectweb.dream.message.MessageManagerType;
import org.objectweb.dream.message.codec.CodecInputOutput;
import org.objectweb.dream.message.codec.MessageCodec;
import org.objectweb.dream.message.codec.SocketCodecInputOutput;
import org.objectweb.dream.protocol.BindException;
import org.objectweb.dream.protocol.ExceptionChunk;
import org.objectweb.dream.protocol.ExportException;
import org.objectweb.dream.protocol.ExportIdentifier;
import org.objectweb.dream.protocol.IPExportIdentifier;
import org.objectweb.dream.protocol.IncomingPush;
import org.objectweb.dream.protocol.InvalidExportIdentifierException;
import org.objectweb.dream.protocol.OutgoingPush;
import org.objectweb.dream.protocol.Protocol;
import org.objectweb.dream.util.Error;
import org.objectweb.dream.util.LoggerUtil;
import org.objectweb.fractal.api.Component;
import org.objectweb.fractal.api.control.IllegalLifeCycleException;
import org.objectweb.fractal.fraclet.annotation.annotations.Attribute;
import org.objectweb.fractal.fraclet.annotation.annotations.Interface;
import org.objectweb.fractal.fraclet.annotation.annotations.Provides;
import org.objectweb.fractal.fraclet.annotation.annotations.Requires;
import org.objectweb.fractal.fraclet.annotation.annotations.Service;
import org.objectweb.util.monolog.api.BasicLevel;
import org.objectweb.util.monolog.api.Logger;

/**
 * Implementation of the TCP/IP protocol.
 */
@DreamComponent(controllerDesc = "activeDreamUnstoppablePrimitive")
@Provides(interfaces = { @Interface(name = Protocol.ITF_NAME, signature = SSLProtocol.class) })
public class SSLProtocolImpl implements SSLProtocol, ConnectionManager {
    /**
     * Variable for debug
     */
    // private static final boolean DEFENSIVE_CHECK = true;
    /**
     * Name associated to the "bind-reply" chunk
     */
    protected static final String BIND_REPLY_CHUNK_NAME = "tcpip-bind-reply";

    // ------------------------------------------------------------------------
    // --
    // Services interfaces
    // ------------------------------------------------------------------------
    // --

    /**
     * Component reference
     */
    @Service
    Component weaveableC;

    /**
     * Logger of the component
     */
    @DreamMonolog()
    protected Logger logger;

    /**
     * Chunk factory used to create exception chunk. In the implementation of
     * the export/bind pattern, this chunk is used to notify that an exception
     * occurred while binding on the server side;
     */
    protected ChunkFactoryReference exceptionChunkFactory;

    /**
     * Set which contains all sessions
     */
    protected Set sessions = new HashSet();

    /**
     * Synchronized Queue which contains available sessions, i.e. sessions that
     * can be used by the reader task to receive messages.
     */
    protected BlockingQueue availableReadingSessions = new LinkedBlockingQueue();

    /**
     * Thread pool manager for the reader task
     */
    protected ThreadPoolManager threadPoolManager;

    /**
     * Task in charge of the message receiving via the {@link #messageCodecItf}
     */
    protected ReaderTask readerTask = new ReaderTask();

    /**
     * Map containing {@link ChannelFactory channel factories} identified by
     * their {@link IPExportIdentifier} (more precisely by their port because
     * the hostname is the same for each {@link ChannelFactory channel factory}
     * ).
     */
    protected Map exportedChannels = new ConcurrentHashMap();

    // ------------------------------------------------------------------------
    // ---
    // Attribute fields
    // ------------------------------------------------------------------------
    // ---

    /**
     * Name of the local host. It should be set to "localhost" in most cases
     * except when virtual IP are used
     */
    @Attribute(argument = "hostName")
    protected String hostName;

    /**
     * The listening port on which the TCP/IP protocol will wait incoming
     * connection for an exported channel.
     */
    @Attribute(argument = "port")
    protected int port;

    // Cannot set a timeout to the SSLSocketFactory!
    // @Attribute(argument = "connectionTimeout")
    // protected int connectionTimeout;

    /**
     * The number of connection retries before aborting.
     */
    @Attribute(argument = "connectionRetry")
    protected short connectionRetry;

    /**
     * Specify if the Nagle's algorithm is disabled.
     */
    @Attribute(argument = "tcpNoDelay")
    protected boolean tcpNoDelay;

    /**
     * the SO_TIMEOUT parameter of created sockets.
     */
    @Attribute(argument = "soTimeout")
    protected int soTimeout;

    /**
     * the SO_LINGER parameter of created sockets. A negative value
     * disable the linger on close.
     */
    @Attribute(argument = "soLinger")
    protected int soLinger;

    /**
     * the SO_KEEPALIVE parameter of created sockets. A {@code
     * false} value disable the keeping of an alive connection on the socket.
     */
    @Attribute(argument = "soKeepAlive")
    protected boolean soKeepAlive;

    /**
     * The keystore file
     */
    @Attribute(argument = "keyStoreFile")
    protected String keyStoreFile;

    /**
     * the keystore password
     */
    @Attribute(argument = "keyStorePassword")
    protected String keyStorePassword;

    /**
     * the key password
     */
    @Attribute(argument = "keyPassword")
    protected String keyPassword;

    /**
     * The truststore file
     */
    @Attribute(argument = "trustStoreFile")
    protected String trustStoreFile;

    /**
     * the truststore password
     */
    @Attribute(argument = "trustStorePassword")
    protected String trustStorePassword;

    // ------------------------------------------------------------------------
    // ---
    // Client interfaces
    // ------------------------------------------------------------------------
    // ---

    @Requires(name = ConnectionFactoryItf.ITF_NAME)
    protected ConnectionFactoryItf connectionFactoryItf;

    @Requires(name = "message-manager")
    protected MessageManagerType messageManagerItf;

    @Requires(name = "message-codec")
    protected MessageCodec messageCodecItf;

    @Requires(name = "task-manager")
    protected TaskManager taskManagerItf;

    // ------------------------------------------------------------------------
    // ---
    // SSL
    // ------------------------------------------------------------------------
    // ---

    /**
     * The key manager factory
     */
    protected KeyManagerFactory keyManagerFactory;

    /**
     * The keystore
     */
    protected KeyStore keyStore;

    /**
     * The truststore
     */
    protected KeyStore trustStore;

    /**
     * The trust manager factory
     */
    protected TrustManagerFactory trustManagerFactory;

    /**
     * The SSL context
     */
    protected SSLContext sslContext;

    /**
     * The SSL socket factory
     */
    protected SSLSocketFactory sslSocketFactory;

    /**
     * The SSL server socket factory
     */
    protected SSLServerSocketFactory sslServerSocketFactory;

    /**
     * Is SSL initialized ?
     */
    protected Boolean sslInitialized = new Boolean(false);

    // ------------------------------------------------------------------------
    // ---
    // Implementation of the ChannelProtocol interface
    // ------------------------------------------------------------------------
    // ---

    /**
     * @see ChannelProtocol#export(ChannelFactory, Map)
     */
    public ExportIdentifier export(ChannelFactory channel, Map hints)
            throws ExportException {
        LoggerUtil.start(this.logger);

        int port = this.port;
        int portRange = 0;
        if (hints != null) {
            if (hints.get(PORT) != null) {
                port = ((Integer) hints.get(PORT)).intValue();
            }
            if (hints.get(PORT_RANGE) != null) {
                portRange = ((Integer) hints.get(PORT_RANGE)).intValue();
            }
        }
        portRange += port;

        ServerSocket serverSocket = null;
        Exception exception = null;
        while ((port <= portRange) && serverSocket == null) {
            exception = null;
            try {
                serverSocket = this.sslServerSocketFactory.createServerSocket(port, 50, InetAddress
                        .getByName(this.hostName));
            } catch (final IOException e) {
                exception = e;
                port++;
            }
        }

        if (exception != null) {
            throw new ExportException("Unable to open server socket : " + exception.getMessage());
        }

        final IPExportIdentifier exportIdentifier = new IPExportIdentifier(this.hostName, port);
        this.exportedChannels.put(exportIdentifier, channel);
        this.connectionFactoryItf.addServerSocket(serverSocket, exportIdentifier);

        LoggerUtil.returnAfter(this.logger);
        return exportIdentifier;
    }

    /**
     * @see ChannelProtocol#unexport(ExportIdentifier)
     */
    public void unexport(ExportIdentifier exportIdentifier) throws InvalidExportIdentifierException {
        if (!(exportIdentifier instanceof IPExportIdentifier)) {
            throw new InvalidExportIdentifierException(
                    "this protocol requires a IPExportIdentifier.", exportIdentifier);
        }
        this.connectionFactoryItf.removeServerSocket((IPExportIdentifier) exportIdentifier);
    }

    /**
     * @param hints
     *            if "localexportidentifier" key is set, this value is used for
     *            the (localaddr,localport) of the socket
     * @see ChannelProtocol#bind(ExportIdentifier, IncomingPush, Map)
     */
    public OutgoingPush bind(ExportIdentifier exportId, IncomingPush toClientPush,
            Map hints) throws InvalidExportIdentifierException, BindException {
        LoggerUtil.start(this.logger);

        // TODO check local binding.
        if (!(exportId instanceof IPExportIdentifier)) {
            throw new InvalidExportIdentifierException(
                    "this protocol requires a IPExportIdentifier.", exportId);
        }
        final IPExportIdentifier identifier = (IPExportIdentifier) exportId;

        IPExportIdentifier localExportIdentifier = null;
        try {
            if (hints != null) {
                final Object o = hints.get(LOCAL_EXPORT_IDENTIFIER_KEY);
                if (o instanceof IPExportIdentifier) {
                    this.logger.log(BasicLevel.ERROR, "localExportIdentifier specified : "
                            + localExportIdentifier);
                    localExportIdentifier = (IPExportIdentifier) o;
                } else {
                    this.logger.log(BasicLevel.ERROR, "the local export identifier specified in "
                            + "the hints map is not an instance of IPExportIdentifier : "
                            + o.getClass().getName());
                }
            }
        } catch (final IllegalStateException e) {
            throw new BindException("Unable to open connection to specified export : ", exportId, e);
        }

        if (localExportIdentifier == null) {
            localExportIdentifier = new IPExportIdentifier(this.hostName, 0);
        }

        Socket clientSocket = null;
        try {
            clientSocket = this.createConnection(identifier, localExportIdentifier);
        } catch (final IOException e) {
            throw new BindException("Unable to open connection to specified export : ", exportId, e);

        }
        Session session;
        try {
            session = new Session(clientSocket, identifier);
        } catch (final IOException e) {
            this.silentClose(clientSocket);
            throw new BindException("unable to create session for opened socket : ", exportId, e);
        }

        // wait for acknowledgment message.
        this.waitAknowledgment(exportId, session, toClientPush);
        this.initSession(session, toClientPush);

        LoggerUtil.returnAfter(this.logger);
        return session;
    }

    // ------------------------------------------------------------------------
    // ---
    // Implementation of the TCPIPProtocol interface
    // ------------------------------------------------------------------------
    // ---

    /**
     * @see org.objectweb.dream.protocol.Protocol#createExportIdentifier(Map,
     *      ExportIdentifier[])
     */
    public ExportIdentifier createExportIdentifier(Map info, ExportIdentifier[] next)
            throws InvalidExportIdentifierException {
        if (next != null && next.length != 0) {
            throw new InvalidExportIdentifierException("TCP/IP protocol is a leaf "
                    + "in the protocol graph, it cannot have next export identifier");
        }
        Object o;
        if (info == null || (o = info.get(ADDRESS)) == null) {
            throw new InvalidExportIdentifierException("Can't find address in info map");
        }
        String hostName;
        if (o instanceof String) {
            hostName = (String) o;
        } else if (o instanceof InetAddress) {
            hostName = ((InetAddress) o).getCanonicalHostName();
        } else {
            throw new InvalidExportIdentifierException(
                    "Invalid address object in info map, must be an InetAddress or a String");
        }

        o = info.get(PORT);
        if (o == null) {
            throw new InvalidExportIdentifierException("Can't find port in info map");

        }
        if (!(o instanceof Number)) {
            throw new InvalidExportIdentifierException(
                    "Invalid port object in info map, must be a Number");
        }
        final int port = ((Number) o).intValue();
        return this.createExportIdentifier(hostName, port);
    }

    /**
     * @see TCPIPProtocol#createExportIdentifier(String, int)
     */
    public IPExportIdentifier createExportIdentifier(String hostName, int port) {
        return new IPExportIdentifier(hostName, port);
    }

    // ------------------------------------------------------------------------
    // ---
    // Implementation of the ConnectionManager interface
    // ------------------------------------------------------------------------
    // ---

    /**
     * @see ConnectionManager#newConnection(Socket, IPExportIdentifier)
     */
    public void newConnection(Socket socket, IPExportIdentifier identifier) {
        LoggerUtil.start(this.logger);

        final ChannelFactory channel = this.exportedChannels.get(identifier);

        final Message message = this.messageManagerItf.createMessage();
        final ExceptionChunk chunk = this.messageManagerItf.createChunk(this.exceptionChunkFactory);
        this.messageManagerItf.addChunk(message, BIND_REPLY_CHUNK_NAME, chunk);
        Session session;
        try {
            session = new Session(socket, identifier);
        } catch (final IOException e) {
            this.silentClose(socket);
            this.logger
                    .log(BasicLevel.ERROR, "Unable to create session for new incoming socket", e);
            return;
        }
        IncomingPush incomingPush = null;
        try {
            incomingPush = channel.instantiate(session);
        } catch (final IOException e) {
            chunk.setException(e);
            this.logger.log(BasicLevel.DEBUG,
                    "Exception caught while instantiating channel factory", e);
        }
        // send the bind reply of the export/bind pattern
        try {
            session.outgoingPush(message);
        } catch (final IOPushException e) {
            this.logger.log(BasicLevel.ERROR, "Unable to send reply to client, close socket", e);
            this.silentClose(session);
            return;
        }
        if (incomingPush != null) {
            this.initSession(session, incomingPush);
        }

        LoggerUtil.end(this.logger);
    }

    // ------------------------------------------------------------------------
    // ---
    // Inner Class
    // ------------------------------------------------------------------------
    // ---

    protected class Session implements OutgoingPush {

        boolean closed = false;

        Socket socket;

        CodecInputOutput codecInputOutput = new SocketCodecInputOutput();

        IPExportIdentifier exportIdentifier;

        IncomingPush upperIncomingPushItf;

        Session(Socket socket, IPExportIdentifier exportIdentifier) throws IOException {
            this.socket = socket;
            this.exportIdentifier = exportIdentifier;
            this.codecInputOutput.setInput(socket.getInputStream());
            this.codecInputOutput.setOutput(socket.getOutputStream());
        }

        synchronized void closeSession() throws IOException {
            LoggerUtil.call(SSLProtocolImpl.this.logger);

            this.closed = true;
            this.codecInputOutput.close();

            LoggerUtil.end(SSLProtocolImpl.this.logger);
        }

        // --------------------------------------------------------------------
        // -----
        // Implementation of the OutgoingPush interface
        // --------------------------------------------------------------------
        // -----

        /**
         * @see OutgoingPush#outgoingPush(Message)
         */
        public synchronized void outgoingPush(Message message) throws IOPushException {
            LoggerUtil.start(SSLProtocolImpl.this.logger);
            
            if (this.closed) {
                throw new IOPushException("Session closed");
            }
            
            try {
                SSLProtocolImpl.this.messageCodecItf.encode(this.codecInputOutput, message);
            } catch (final IOException e) {
                if (!this.closed) {
                    SSLProtocolImpl.this.logger.log(BasicLevel.INFO,
                            "An error occurs while writing message on TCP/IP socket", e);
                    SSLProtocolImpl.this.sessionError(this, e);
                }
                throw new IOPushException("unable to send message", e);
            }
            SSLProtocolImpl.this.messageManagerItf.deleteMessage(message);
            
            LoggerUtil.end(SSLProtocolImpl.this.logger);
        }

        /**
         * @see OutgoingPush#outgoingClose(IncomingPush)
         */
        public void outgoingClose(IncomingPush incomingPush) throws IOException {
            this.closeSession();
            SSLProtocolImpl.this.sessions.remove(this);
        }

        /**
         * Nice format for output
         */
        @Override
        public String toString() {
            String res = "";
            if (this.exportIdentifier == null) {
                res = "AnonymousSSLSession@" + Integer.toHexString(this.hashCode()) + "(";
            } else {
                res = "SSLSession@" + Integer.toHexString(this.hashCode()) + "(id:"
                        + this.exportIdentifier + ", localSocket(" + this.socket.getLocalAddress()
                        + ":" + this.socket.getLocalPort() + "), remoteSocket("
                        + this.socket.getInetAddress() + ":" + this.socket.getPort() + "), ";
            }
            res = "State : " + (this.closed ? "closed" : "open") + ")";
            return res;
        }
    }

    protected class ReaderTask extends AbstractTask {

        public ReaderTask() {
            super("SSL/IP reader task");
        }

        // --------------------------------------------------------------------
        // -----
        // Implementation of the Task interface
        // --------------------------------------------------------------------
        // -----

        /**
         * @see org.objectweb.dream.control.activity.task.Task#execute(Object)
         */
        public Object execute(Object hints) throws InterruptedException {
            Session session;
            session = SSLProtocolImpl.this.availableReadingSessions.poll(5, TimeUnit.SECONDS);
            if (session == null) {
                SSLProtocolImpl.this.logger.log(BasicLevel.INFO,
                        "No available session for the reader task");
                return STOP_EXECUTING;
            }
            if (session.closed) {
                SSLProtocolImpl.this.logger.log(BasicLevel.INFO, session
                        + " is closed, this task will be stopped");
                return STOP_EXECUTING;
            }
            Message message;
            try {
                message = SSLProtocolImpl.this.messageCodecItf.decode(session.codecInputOutput);
            } catch (final IOException e) {
                synchronized (this) {
                    // test if the session was properly closed and causes this
                    // exception (= closeSession() method called)
                    if (!session.closed) {
                        SSLProtocolImpl.this.logger.log(BasicLevel.INFO,
                                "An error occurs while reading message on SSL socket", e);
                        SSLProtocolImpl.this.sessionError(session, e);
                    }
                }
                return STOP_EXECUTING;
            }
            try {
                session.upperIncomingPushItf.incomingPush(message);
                SSLProtocolImpl.this.logger.log(BasicLevel.DEBUG,
                        "Sending a message to the upper level protocol");
            } catch (final PushException e) {
                SSLProtocolImpl.this.logger.log(BasicLevel.ERROR,
                        "Exception catch while pushing incoming message. delete message.", e);
                SSLProtocolImpl.this.messageManagerItf.deleteMessage(message);
            }
                SSLProtocolImpl.this.availableReadingSessions.put(session);
            
            return EXECUTE_AGAIN;
        }

        /**
         * @see org.objectweb.dream.control.activity.task.Task#registered(Object)
         */
        @Override
        public void registered(Object controlItf) {
            SSLProtocolImpl.this.setThreadPoolManager((ThreadPoolManager) controlItf);
        }
    }

    // ------------------------------------------------------------------------
    // ---
    // Utility methods
    // ------------------------------------------------------------------------
    // ---

    /**
     * Close the session and ignore potential IOException
     * 
     * @param session
     *            the session to close
     */
    protected final void silentClose(Session session) {
        try {
            session.closeSession();
        } catch (final IOException e) {
            // ignore
            this.logger.log(BasicLevel.DEBUG, "An error occured while closing " + session + " : "
                    + e.getMessage());
        }
    }

    /**
     * Close the socket and ignore potential IOException
     * 
     * @param socket
     *            the socket to close
     */
    protected final void silentClose(Socket socket) {
        LoggerUtil.call(this.logger);
        try {
            socket.shutdownInput();
            socket.shutdownOutput();
            socket.close();
        } catch (final IOException e) {
            // ignore
            this.logger.log(BasicLevel.DEBUG, "An error occured while closing a socket : "
                    + e.getMessage());
        }
    }

    /**
     * @param session
     * @param upperIncomingPush
     *            incoming interface of the upper session
     * @return
     */
    protected synchronized Session initSession(Session session, IncomingPush upperIncomingPush) {
        LoggerUtil.start(this.logger);
        
        session.upperIncomingPushItf = upperIncomingPush;
        synchronized (this.sessions) {
            this.sessions.add(session);
            if (session.upperIncomingPushItf != null) {
                try {
                    this.availableReadingSessions.put(session);
                } catch (final InterruptedException e1) {
                    Error.bug(this.logger, e1);
                }
                if (this.threadPoolManager != null) {
                    try {
                        this.logger.log(BasicLevel.DEBUG, "add a thread in thread pool");
                        this.threadPoolManager.addThread(this.readerTask);
                    } catch (final Exception e) {
                        Error.bug(this.logger, e);
                    }
                }
            }
            
            LoggerUtil.returnAfter(this.logger);
        }
        return session;
    }

    protected void setThreadPoolManager(ThreadPoolManager threadPoolManager) {
        this.threadPoolManager = threadPoolManager;
        
        synchronized (this.sessions) {
            for (final Session s : this.sessions) {
                try {
                    this.logger.log(BasicLevel.DEBUG, "add a thread in thread pool for " + s);
                    threadPoolManager.addThread(this.readerTask);
                    this.availableReadingSessions.put(s);
                } catch (final ThreadPoolOverflowException e) {
                    this.logger.log(BasicLevel.WARN, "Unable to add reader thread", e);
                } catch (final IllegalTaskException e) {
                    Error.bug(this.logger, e);
                } catch (final InterruptedException e) {
                    Error.bug(this.logger, e);
                }
            }
        }
    }

    protected void sessionError(Session session, Exception exception) {
            this.sessions.remove(session);
        this.silentClose(session);
        if (session.upperIncomingPushItf != null) {
            // session is a server session, inform channel.
            session.upperIncomingPushItf.incomingClosed(session, exception);
        }
    }

    /**
     * Try to create a connection toward the destination specified via the
     * export identifier. the number of try depends on the value of the
     * connectionRetry fractal attribute.
     * 
     * @param exportIdentifier
     *            identifier the destination
     * @param localExportIdentifier
     * @return the created socket
     * @throws IOException
     *             if an error occured while connecting
     * @throws CertificateException
     * @throws NoSuchAlgorithmException
     * @throws KeyStoreException
     * @throws UnrecoverableKeyException
     * @throws KeyManagementException
     */
    protected Socket createConnection(IPExportIdentifier exportIdentifier,
            IPExportIdentifier localExportIdentifier) throws IOException {
        IOException exception = null;
        Socket clientSocket = null;

        synchronized (this.sslInitialized) {
            if (!this.sslInitialized) {
                throw new IllegalStateException("SSLProtocol is not initialized");
            }
        }

        for (int i = 0; i <= this.connectionRetry; i++) {
            try {
                this.logger.log(BasicLevel.DEBUG, "try to open a SSL connection");

                if (localExportIdentifier == null) {
                    clientSocket = this.sslSocketFactory.createSocket(exportIdentifier
                            .getHostName(), exportIdentifier.getPort());
                } else {
                    clientSocket = this.sslSocketFactory.createSocket(exportIdentifier
                            .getHostName(), exportIdentifier.getPort(), InetAddress
                            .getByName(localExportIdentifier.getHostName()), localExportIdentifier
                            .getPort());
                }
                this.logger.log(BasicLevel.DEBUG, "connected to " + exportIdentifier.getHostName()
                        + ":" + exportIdentifier.getPort());

                // TODO: Check if the setting of the parameters is working after
                // having connected the SSL socket
                this.setSocketOption(clientSocket);

                break;
            } catch (final IOException exc) {
                exception = exc;
                this.logger.log(BasicLevel.INFO, "connection failed on address "
                        + exportIdentifier.getHostName() + ":" + exportIdentifier.getPort()
                        + ". Another attempt will be done at " + i * 250 + " ms");
                try {
                    Thread.sleep(i * 250);
                } catch (final InterruptedException ignored) {
                    break;
                }
            }
        }

        if (exception != null) {
            throw exception;
        } else {
            return clientSocket;
        }
    }

    protected void setSocketOption(Socket sock) throws IOException {
        sock.setTcpNoDelay(this.tcpNoDelay);
        sock.setSoTimeout(this.soTimeout);
        sock.setKeepAlive(this.soKeepAlive);
        if (this.soLinger >= 0) {
            sock.setSoLinger(true, this.soLinger);
        } else {
            sock.setSoLinger(false, 0);
        }
        if (this.logger.isLoggable(BasicLevel.DEBUG)) {
            this.logger.log(BasicLevel.DEBUG, "Options for socket (" + sock.getInetAddress() + ","
                    + sock.getPort() + ") : tcpNoDelay = " + sock.getTcpNoDelay() + " soLinger = "
                    + sock.getSoLinger() + " soTimeout = " + sock.getSoTimeout()
                    + " soKeepAlive = " + sock.getKeepAlive() + " sendBufferSize = "
                    + sock.getSendBufferSize());
        }
    }

    // -------------------------------------------------------------------------
    // Dream Components Lifecycle Methods
    // -------------------------------------------------------------------------

    /**
     * Method executed during the first start of the component
     */
    @DreamLifeCycle(on = DreamLifeCycleType.FIRST_START)
    synchronized protected void beforeFirstStart(Component componentItf)
            throws IllegalLifeCycleException {

        this.logger.log(BasicLevel.DEBUG,
                "First start of the component : SSL initialisation + SSL reader task added");

        try {
            synchronized (this.sslInitialized) {
                if (!this.sslInitialized) {
                    this.initSSL();
                }
            }

            final Map hints = new HashMap();
            hints.put("thread", "pool");
            Util.addTask(componentItf, this.readerTask, hints);
        } catch (final Exception e) {
            this.logger.log(BasicLevel.FATAL, e);
            throw new IllegalLifeCycleException("Failed to Prepare first start. Caused : "
                    + e.getClass().getSimpleName() + " - " + e.getMessage());
        }

        this.exceptionChunkFactory = this.messageManagerItf.getChunkFactory(ExceptionChunk.class);
    }

    /**
     * @see org.objectweb.dream.control.lifecycle.PrepareStopLifeCycleController#
     *      prepareStopFc()
     */
    @DreamLifeCycle(on = DreamLifeCycleType.PREPARE_TO_STOP)
    public void prepareStopFc() throws IllegalLifeCycleException {
        synchronized (this.sessions) {

            this.logger.log(BasicLevel.DEBUG,
                    "Prepare stop of the component : Close every opened SSL sockets");

            for (final Session session : this.sessions) {
                this.logger.log(BasicLevel.DEBUG, "Going to close " + session);
                this.silentClose(session);
            }
            this.sessions.clear();
        }
    }

    /**
     * @see TCPIPProtocolImplAttributeController#setHostName(String)
     */
    public void setHostName(String hostname) throws UnknownHostException {
        if (hostname.trim().equalsIgnoreCase("localhost")) {
            this.hostName = InetAddress.getLocalHost().getHostName();
        } else {
            // this call make a check on the given parameter
            this.hostName = InetAddress.getByName(hostname).getHostName();
        }
    }

    /**
     * Initialize the SSL environment.
     * 
     * @throws KeyStoreException
     * @throws NoSuchAlgorithmException
     * @throws CertificateException
     * @throws FileNotFoundException
     * @throws IOException
     * @throws KeyManagementException
     * @throws UnrecoverableKeyException
     */
    private synchronized void initSSL() throws KeyStoreException, NoSuchAlgorithmException,
            CertificateException, FileNotFoundException, IOException, KeyManagementException,
            UnrecoverableKeyException {
        this.keyManagerFactory = KeyManagerFactory.getInstance(KeyManagerFactory
                .getDefaultAlgorithm());

        this.keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
        this.keyStore.load(new FileInputStream(this.keyStoreFile), this.keyStorePassword
                .toCharArray());

        this.trustStore = KeyStore.getInstance(KeyStore.getDefaultType());
        this.trustStore.load(new FileInputStream(this.trustStoreFile), this.trustStorePassword
                .toCharArray());

        this.keyManagerFactory.init(this.keyStore, this.keyPassword.toCharArray());

        this.trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory
                .getDefaultAlgorithm());

        this.trustManagerFactory.init(this.trustStore);

        this.sslContext = SSLContext.getInstance("TLS");
        this.sslContext.init(this.keyManagerFactory.getKeyManagers(), this.trustManagerFactory
                .getTrustManagers(), null);

        this.sslSocketFactory = this.sslContext.getSocketFactory();

        this.sslServerSocketFactory = this.sslContext.getServerSocketFactory();

        this.sslInitialized = true;
    }

    /**
     * Wait the bind acknowledgment.
     * 
     * @param exportId
     * @param session
     * @param toClientPush
     * @throws BindException
     */
    private void waitAknowledgment(ExportIdentifier exportId, Session session,
            IncomingPush toClientPush) throws BindException {
        Message message;
        try {
            message = this.messageCodecItf.decode(session.codecInputOutput);
        } catch (final IOException e) {
            this.silentClose(session);
            throw new BindException("Unable to receive acknowledgment message", exportId, e);
        }
        ExceptionChunk chunk = this.messageManagerItf.getChunk(message, BIND_REPLY_CHUNK_NAME);
        if (chunk == null) {
            if ((chunk = this.messageManagerItf.getChunk(message,
                    ChannelFactory.BIND_REPLY_CHUNK_NAME)) != null) {
                // it's an BIND REPLY CHUNK for the upper protocol
                try {
                    toClientPush.incomingPush(message);
                } catch (final PushException e) {
                    Error.error("First received message on socket does contain "
                            + "a bind-reply chunk for the channel factory "
                            + "and it cannot be transmitted to it", this.logger);
                }
                // recursive call
                this.waitAknowledgment(exportId, session, toClientPush);
            } else {
                this.silentClose(session);
                Error.error("First received message on socket does not contain "
                        + "acknowledgment chunk", this.logger);
            }
        } else {
            if (chunk.getException() != null) {
                throw new BindException("Remote exception", exportId, chunk.getException());
            }
        }
        this.messageManagerItf.deleteMessage(message);
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy