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

quickfix.mina.initiator.IoSessionInitiator Maven / Gradle / Ivy

/*******************************************************************************
 * Copyright (c) quickfixengine.org  All rights reserved.
 *
 * This file is part of the QuickFIX FIX Engine
 *
 * This file may be distributed under the terms of the quickfixengine.org
 * license as defined by quickfixengine.org and appearing in the file
 * LICENSE included in the packaging of this file.
 *
 * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING
 * THE WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A
 * PARTICULAR PURPOSE.
 *
 * See http://www.quickfixengine.org/LICENSE for licensing information.
 *
 * Contact [email protected] if any conditions of this licensing
 * are not clear to you.
 ******************************************************************************/

package quickfix.mina.initiator;

import org.apache.mina.core.filterchain.IoFilterChainBuilder;
import org.apache.mina.core.future.ConnectFuture;
import org.apache.mina.core.service.IoConnector;
import org.apache.mina.core.session.IoSession;
import org.apache.mina.filter.codec.ProtocolCodecFilter;
import org.apache.mina.proxy.ProxyConnector;
import org.apache.mina.transport.socket.SocketConnector;
import quickfix.ConfigError;
import quickfix.LogUtil;
import quickfix.Session;
import quickfix.SystemTime;
import quickfix.mina.CompositeIoFilterChainBuilder;
import quickfix.mina.EventHandlingStrategy;
import quickfix.mina.NetworkingOptions;
import quickfix.mina.ProtocolFactory;
import quickfix.mina.message.FIXProtocolCodecFactory;
import quickfix.mina.ssl.SSLConfig;
import quickfix.mina.ssl.SSLContextFactory;
import quickfix.mina.ssl.SSLFilter;
import quickfix.mina.ssl.SSLSupport;

import javax.net.ssl.SSLContext;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.security.GeneralSecurityException;
import java.util.Arrays;
import java.util.concurrent.Future;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import quickfix.mina.SessionConnector;

public class IoSessionInitiator {
    private final static long CONNECT_POLL_TIMEOUT = 2000L;
    private final ScheduledExecutorService executor;
    private final ConnectTask reconnectTask;

    private Future reconnectFuture;

    public IoSessionInitiator(Session fixSession, SocketAddress[] socketAddresses,
            SocketAddress localAddress, int[] reconnectIntervalInSeconds,
            ScheduledExecutorService executor, NetworkingOptions networkingOptions,
            EventHandlingStrategy eventHandlingStrategy,
            IoFilterChainBuilder userIoFilterChainBuilder, boolean sslEnabled, SSLConfig sslConfig,
            String proxyType, String proxyVersion, String proxyHost, int proxyPort,
            String proxyUser, String proxyPassword, String proxyDomain, String proxyWorkstation) throws ConfigError {
        this.executor = executor;
        final long[] reconnectIntervalInMillis = new long[reconnectIntervalInSeconds.length];
        for (int ii = 0; ii != reconnectIntervalInSeconds.length; ++ii) {
            reconnectIntervalInMillis[ii] = reconnectIntervalInSeconds[ii] * 1000L;
        }
        try {
            reconnectTask = new ConnectTask(sslEnabled, socketAddresses, localAddress,
                    userIoFilterChainBuilder, fixSession, reconnectIntervalInMillis,
                    networkingOptions, eventHandlingStrategy, sslConfig,
                    proxyType, proxyVersion, proxyHost, proxyPort, proxyUser, proxyPassword, proxyDomain, proxyWorkstation);
        } catch (GeneralSecurityException e) {
            throw new ConfigError(e);
        }

        fixSession.getLog().onEvent("Configured socket addresses for session: " + Arrays.asList(socketAddresses));
    }

    private static class ConnectTask implements Runnable {
        private final boolean sslEnabled;
        private final SocketAddress[] socketAddresses;
        private final SocketAddress localAddress;
        private final IoFilterChainBuilder userIoFilterChainBuilder;
        private IoConnector ioConnector;
        private final Session fixSession;
        private final long[] reconnectIntervalInMillis;
        private final NetworkingOptions networkingOptions;
        private final EventHandlingStrategy eventHandlingStrategy;
        private final SSLConfig sslConfig;

        private IoSession ioSession;
        private long lastReconnectAttemptTime;
        private long lastConnectTime;
        private int nextSocketAddressIndex;
        private int connectionFailureCount;
        private ConnectFuture connectFuture;

        private final String proxyType;
        private final String proxyVersion;
        private final String proxyHost;
        private final int proxyPort;
        private final String proxyUser;
        private final String proxyPassword;
        private final String proxyDomain;
        private final String proxyWorkstation;

        public ConnectTask(boolean sslEnabled, SocketAddress[] socketAddresses,
                SocketAddress localAddress, IoFilterChainBuilder userIoFilterChainBuilder,
                Session fixSession, long[] reconnectIntervalInMillis,
                NetworkingOptions networkingOptions, EventHandlingStrategy eventHandlingStrategy, SSLConfig sslConfig,
                String proxyType, String proxyVersion, String proxyHost,
                int proxyPort, String proxyUser, String proxyPassword, String proxyDomain,
                String proxyWorkstation) throws ConfigError, GeneralSecurityException {
            this.sslEnabled = sslEnabled;
            this.socketAddresses = socketAddresses;
            this.localAddress = localAddress;
            this.userIoFilterChainBuilder = userIoFilterChainBuilder;
            this.fixSession = fixSession;
            this.reconnectIntervalInMillis = reconnectIntervalInMillis;
            this.networkingOptions = networkingOptions;
            this.eventHandlingStrategy = eventHandlingStrategy;
            this.sslConfig = sslConfig;

            this.proxyType = proxyType;
            this.proxyVersion = proxyVersion;
            this.proxyHost = proxyHost;
            this.proxyPort = proxyPort;
            this.proxyUser = proxyUser;
            this.proxyPassword = proxyPassword;
            this.proxyDomain = proxyDomain;
            this.proxyWorkstation = proxyWorkstation;

            setupIoConnector();
        }

        private void setupIoConnector() throws ConfigError, GeneralSecurityException {
            final CompositeIoFilterChainBuilder ioFilterChainBuilder = new CompositeIoFilterChainBuilder(userIoFilterChainBuilder);

            boolean hasProxy = proxyType != null && proxyPort > 0 && socketAddresses[nextSocketAddressIndex] instanceof InetSocketAddress;

            SSLFilter sslFilter = null;
            if (sslEnabled) {
                sslFilter = installSslFilter(ioFilterChainBuilder, !hasProxy);
            }

            ioFilterChainBuilder.addLast(FIXProtocolCodecFactory.FILTER_NAME, new ProtocolCodecFilter(new FIXProtocolCodecFactory()));

            IoConnector newConnector;
            newConnector = ProtocolFactory.createIoConnector(socketAddresses[nextSocketAddressIndex]);
            newConnector.setHandler(new InitiatorIoHandler(fixSession, networkingOptions, eventHandlingStrategy));
            newConnector.setFilterChainBuilder(ioFilterChainBuilder);

            if (hasProxy) {
                ProxyConnector proxyConnector = ProtocolFactory.createIoProxyConnector(
                        (SocketConnector) newConnector,
                        (InetSocketAddress) socketAddresses[nextSocketAddressIndex],
                        new InetSocketAddress(proxyHost, proxyPort),
                        proxyType, proxyVersion, proxyUser, proxyPassword, proxyDomain, proxyWorkstation
                );

                proxyConnector.setHandler(new InitiatorProxyIoHandler(
                        new InitiatorIoHandler(fixSession, networkingOptions, eventHandlingStrategy),
                        sslFilter
                ));

                newConnector = proxyConnector;
            }

            if (ioConnector != null) {
                ioConnector.dispose();
            }
            ioConnector = newConnector;
        }

        private SSLFilter installSslFilter(CompositeIoFilterChainBuilder ioFilterChainBuilder, boolean autoStart)
                throws GeneralSecurityException {
            final SSLContext sslContext = SSLContextFactory.getInstance(sslConfig);
            final SSLFilter sslFilter = new SSLFilter(sslContext, autoStart);
            sslFilter.setUseClientMode(true);
            sslFilter.setCipherSuites(sslConfig.getEnabledCipherSuites() != null ? sslConfig.getEnabledCipherSuites()
                    : SSLSupport.getDefaultCipherSuites(sslContext));
            sslFilter.setEnabledProtocols(sslConfig.getEnabledProtocols() != null ? sslConfig.getEnabledProtocols()
                    : SSLSupport.getSupportedProtocols(sslContext));
            ioFilterChainBuilder.addLast(SSLSupport.FILTER_NAME, sslFilter);
            return sslFilter;
        }

        public synchronized void run() {
            resetIoConnector();
            try {
                if (connectFuture == null) {
                    if (shouldReconnect()) {
                        connect();
                    }
                } else {
                    pollConnectFuture();
                }
            } catch (Throwable e) {
                LogUtil.logThrowable(fixSession.getLog(), "Exception during ConnectTask run", e);
            }
        }

        private void connect() {
            try {
                lastReconnectAttemptTime = SystemTime.currentTimeMillis();
                SocketAddress nextSocketAddress = getNextSocketAddress();
                if (localAddress == null) {
                    connectFuture = ioConnector.connect(nextSocketAddress);
                } else {
                    // QFJ-482
                    connectFuture = ioConnector.connect(nextSocketAddress, localAddress);
                }
                pollConnectFuture();
            } catch (Throwable e) {
                handleConnectException(e);
            }
        }

        private void pollConnectFuture() {
            try {
                connectFuture.awaitUninterruptibly(CONNECT_POLL_TIMEOUT);
                if (connectFuture.getSession() != null) {
                    ioSession = connectFuture.getSession();
                    connectionFailureCount = 0;
                    lastConnectTime = System.currentTimeMillis();
                    connectFuture = null;
                } else {
                    fixSession.getLog().onEvent(
                            "Pending connection not established after "
                                    + (System.currentTimeMillis() - lastReconnectAttemptTime)
                                    + " ms.");
                }
            } catch (Throwable e) {
                handleConnectException(e);
            }
        }

        private void handleConnectException(Throwable e) {
            ++connectionFailureCount;
            SocketAddress socketAddress = socketAddresses[getCurrentSocketAddressIndex()];
            unresolveCurrentSocketAddress(socketAddress);
            while (e.getCause() != null) {
                e = e.getCause();
            }
            final String nextRetryMsg = " (Next retry in " + computeNextRetryConnectDelay() + " milliseconds)";
            if (e instanceof IOException) {
                fixSession.getLog().onErrorEvent(e.getClass().getName() + " during connection to " + socketAddress + ": " + e + nextRetryMsg);
            } else {
                LogUtil.logThrowable(fixSession.getLog(), "Exception during connection to " + socketAddress + nextRetryMsg, e);
            }
            connectFuture = null;
        }

        private SocketAddress getNextSocketAddress() {
            SocketAddress socketAddress = socketAddresses[nextSocketAddressIndex];

            // QFJ-266 Recreate socket address for unresolved addresses
            if (socketAddress instanceof InetSocketAddress) {
                InetSocketAddress inetAddr = (InetSocketAddress) socketAddress;
                if (inetAddr.isUnresolved()) {
                    socketAddress = new InetSocketAddress(inetAddr.getHostName(), inetAddr
                            .getPort());
                    socketAddresses[nextSocketAddressIndex] = socketAddress;
                }
            }
            nextSocketAddressIndex = (nextSocketAddressIndex + 1) % socketAddresses.length;
            return socketAddress;
        }

        // QFJ-822 Reset cached DNS resolution information on connection failure.
        private void unresolveCurrentSocketAddress(SocketAddress socketAddress) {
            if (socketAddress instanceof InetSocketAddress) {
                InetSocketAddress inetAddr = (InetSocketAddress) socketAddress;
                socketAddresses[getCurrentSocketAddressIndex()] = InetSocketAddress.createUnresolved(
                        inetAddr.getHostString(), inetAddr.getPort());
            }
        }

        private int getCurrentSocketAddressIndex() {
            return (nextSocketAddressIndex + socketAddresses.length - 1) % socketAddresses.length;
        }

        private boolean shouldReconnect() {
            return (ioSession == null || !ioSession.isConnected()) && isTimeForReconnect()
                    && (fixSession.isEnabled() && fixSession.isSessionTime());
        }

        private long computeNextRetryConnectDelay() {
            int index = connectionFailureCount - 1;
            if (index < 0)
                index = 0;
            long millis;
            if (index >= reconnectIntervalInMillis.length) {
                millis = reconnectIntervalInMillis[reconnectIntervalInMillis.length - 1];
            } else {
                millis = reconnectIntervalInMillis[index];
            }
            return millis;
        }

        private boolean isTimeForReconnect() {
            return SystemTime.currentTimeMillis() - lastReconnectAttemptTime >= computeNextRetryConnectDelay();
        }

        // TODO JMX Expose reconnect property

        @SuppressWarnings("unused") // exposed via JMX
        public synchronized int getConnectionFailureCount() {
            return connectionFailureCount;
        }

        @SuppressWarnings("unused") // exposed via JMX
        public synchronized long getLastReconnectAttemptTime() {
            return lastReconnectAttemptTime;
        }

        @SuppressWarnings("unused") // exposed via JMX
        public synchronized long getLastConnectTime() {
            return lastConnectTime;
        }

        public Session getFixSession() {
            return fixSession;
        }

        private void resetIoConnector() {
            if (ioSession != null && Boolean.TRUE.equals(ioSession.getAttribute(SessionConnector.QFJ_RESET_IO_CONNECTOR))) {
                try {
                    setupIoConnector();
                    if (connectFuture != null) {
                        connectFuture.cancel();
                    }
                    connectFuture = null;
                    ioSession = null;
                } catch (Throwable e) {
                    LogUtil.logThrowable(fixSession.getLog(), "Exception during resetIoConnector call", e);
                }
            }
        }
    }

    synchronized void start() {
        if (reconnectFuture == null) {
            // The following logon reenabled the session. The actual logon will take
            // place as a side-effect of the session timer task (not the reconnect task).
            reconnectTask.getFixSession().logon(); // only enables the session
            reconnectFuture = executor
                    .scheduleWithFixedDelay(reconnectTask, 0, 1, TimeUnit.SECONDS);
        }
    }

    synchronized void stop() {
        if (reconnectFuture != null) {
            reconnectFuture.cancel(true);
            reconnectFuture = null;
        }
        // QFJ-849: clean up resources of MINA connector
        reconnectTask.ioConnector.dispose();
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy