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

quickfix.mina.SessionConnector Maven / Gradle / Ivy

There is a newer version: 2.3.1
Show newest version
/*******************************************************************************
 * 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;

import org.apache.mina.core.filterchain.IoFilterChainBuilder;
import org.apache.mina.core.session.IoSession;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import quickfix.ConfigError;
import quickfix.Connector;
import quickfix.FieldConvertError;
import quickfix.Session;
import quickfix.SessionFactory;
import quickfix.SessionID;
import quickfix.SessionSettings;
import quickfix.field.converter.IntConverter;

import java.beans.PropertyChangeListener;
import java.beans.PropertyChangeSupport;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.TimeUnit;

/**
 * An abstract base class for acceptors and initiators. Provides support for common functionality and also serves as an
 * abstraction where the code doesn't need to make the acceptor/initiator distinction.
 */
public abstract class SessionConnector implements Connector {
    protected static final int DEFAULT_QUEUE_CAPACITY = 10000;
    public static final String SESSIONS_PROPERTY = "sessions";
    public final static String QF_SESSION = "QF_SESSION";

    protected final Logger log = LoggerFactory.getLogger(getClass());

    protected final PropertyChangeSupport propertyChangeSupport = new PropertyChangeSupport(this);

    private Map sessions = Collections.emptyMap();
    private final SessionSettings settings;
    private final SessionFactory sessionFactory;
    private final static ScheduledExecutorService scheduledExecutorService = Executors
            .newSingleThreadScheduledExecutor(new QFTimerThreadFactory());
    private ScheduledFuture sessionTimerFuture;
    private IoFilterChainBuilder ioFilterChainBuilder;

    public SessionConnector(SessionSettings settings, SessionFactory sessionFactory) throws ConfigError {
        this.settings = settings;
        this.sessionFactory = sessionFactory;
        if (settings == null) {
            throw new ConfigError("no settings");
        }
    }

    public void addPropertyChangeListener(PropertyChangeListener listener) {
        propertyChangeSupport.addPropertyChangeListener(listener);
    }

    public void removePropertyChangeListener(PropertyChangeListener listener) {
        propertyChangeSupport.removePropertyChangeListener(listener);
    }

    protected void setSessions(Map sessions) {
        this.sessions = sessions;
        propertyChangeSupport.firePropertyChange(SESSIONS_PROPERTY, null, sessions);
    }

    /**
     * Get the list of session managed by this connector.
     *
     * @return list of quickfix.Session objects
     * @see quickfix.Session
     */
    public List getManagedSessions() {
        return new ArrayList<>(sessions.values());
    }

    /**
     * This is for subclasses to directly access the session map.
     *
     * @return a map of sessions keys by session ID
     */
    protected Map getSessionMap() {
        return Collections.unmodifiableMap(sessions);
    }

    /**
     * Return the list of session identifiers of sessions managed
     * by this connector. Should be called getSessionIDs but the
     * current name is retained for QF/JNI compatibility.
     *
     * @return list of session identifiers
     */
    public ArrayList getSessions() {
        return new ArrayList<>(sessions.keySet());
    }

    public void addDynamicSession(Session inSession) {
        sessions.put(inSession.getSessionID(), inSession);
        log.debug("adding session for " + inSession.getSessionID());
        propertyChangeSupport.firePropertyChange(SESSIONS_PROPERTY, null, sessions);
    }

    public void removeDynamicSession(SessionID inSessionID) {
        sessions.remove(inSessionID);
        log.debug("removing session for " + inSessionID);
        propertyChangeSupport.firePropertyChange(SESSIONS_PROPERTY, null, sessions);
    }

    public SessionSettings getSettings() {
        return settings;
    }

    protected Session createSession(SessionID sessionID) throws ConfigError {
        return sessionFactory.create(sessionID, settings);
    }

    protected int getIntSetting(String key) throws ConfigError {
        try {
            return IntConverter.convert(settings.getString(key));
        } catch (FieldConvertError e) {
            throw (ConfigError) new ConfigError(e.getMessage()).fillInStackTrace();
        }
    }

    /**
     * Check if we have at least one session and that all the sessions are logged on
     *
     * @return false if no session or at least one session is not logged on
     */
    public boolean isLoggedOn() {
        // if no session, not logged on
        if (sessions.isEmpty())
            return false;
        for (Session session : sessions.values()) {
            // at least one session not logged on
            if (!session.isLoggedOn())
                return false;
        }
        // all the sessions are logged on
        return true;
    }

    private Set getLoggedOnSessions() {
        Set loggedOnSessions = new HashSet<>(sessions.size());
        for (Session session : sessions.values()) {
            if (session.isLoggedOn()) {
                loggedOnSessions.add(session);
            }
        }
        return loggedOnSessions;
    }

    protected void logoutAllSessions(boolean forceDisconnect) {
        log.info("Logging out all sessions");
        if (sessions == null) {
            log.error("Attempt to logout all sessions before initialization is complete.");
            return;
        }
        for (Session session : sessions.values()) {
            try {
                session.logout();
            } catch (Throwable e) {
                logError(session.getSessionID(), null, "Error during logout", e);
            }
        }

        if (forceDisconnect && isLoggedOn()) {
            for (Session session : sessions.values()) {
                try {
                    if (session.isLoggedOn()) {
                        session.disconnect("Forcibly disconnecting session", false);
                    }
                } catch (Throwable e) {
                    logError(session.getSessionID(), null, "Error during disconnect", e);
                }
            }
        }

        if (!forceDisconnect) {
            waitForLogout();
        }
    }

    protected void waitForLogout() {
        long start = System.currentTimeMillis();
        Set loggedOnSessions;
        while (!(loggedOnSessions = getLoggedOnSessions()).isEmpty()) {
            try {
                Thread.sleep(100L);
            } catch (InterruptedException e) {
                log.error(e.getMessage(), e);
            }
            final long elapsed = System.currentTimeMillis() - start;
            Iterator sessionItr = loggedOnSessions.iterator();
            while (sessionItr.hasNext()) {
                Session session = sessionItr.next();
                if (elapsed >= session.getLogoutTimeout() * 1000L) {
                    try {
                        session.disconnect("Logout timeout, force disconnect", false);
                    } catch (IOException e) {
                        log.error(e.getMessage(), e);
                    }
                    sessionItr.remove();
                }
            }
            // Be sure we don't look forever
            if (elapsed > 60000L) {
                log.warn("Stopping session logout wait after 1 minute");
                break;
            }
        }
    }

    protected void logError(SessionID sessionID, IoSession protocolSession, String message, Throwable t) {
        log.error(message + getLogSuffix(sessionID, protocolSession), t);
    }

    private String getLogSuffix(SessionID sessionID, IoSession protocolSession) {
        String suffix = ":";
        if (sessionID != null) {
            suffix += "sessionID=" + sessionID.toString() + ";";
        }
        if (protocolSession != null) {
            suffix += "address=" + protocolSession.getRemoteAddress();
        }
        return suffix;
    }

    protected void startSessionTimer() {
        sessionTimerFuture = scheduledExecutorService.scheduleAtFixedRate(new SessionTimerTask(), 0, 1000L,
                TimeUnit.MILLISECONDS);
        log.info("SessionTimer started");
    }

    protected void stopSessionTimer() {
        if (sessionTimerFuture != null) {
            if (sessionTimerFuture.cancel(false))
                log.info("SessionTimer canceled");
        }
    }

    protected ScheduledExecutorService getScheduledExecutorService() {
        return scheduledExecutorService;
    }

    private class SessionTimerTask implements Runnable {
        public void run() {
            try {
                for (Session session : sessions.values()) {
                    try {
                        session.next();
                    } catch (IOException e) {
                        logError(session.getSessionID(), null, "Error in session timer processing", e);
                    }
                }
            } catch (Throwable e) {
                log.error("Error during timer processing", e);
            }
        }
    }

    private static class QFTimerThreadFactory implements ThreadFactory {

        public Thread newThread(Runnable runnable) {
            Thread thread = new Thread(runnable, "QFJ Timer");
            thread.setDaemon(true);
            return thread;
        }
    }

    /**
     * Allows a custom IOFilterChainBuilder to be added to the session connector. This will allow modification of the
     * MINA filter chain. Modifying the filter chain can be useful for logging, encryption/SSL and other purposes. The
     * FIX codec filter name can be used to for inserting custom filters before or after the FIX message codec.
     *
     * @param ioFilterChainBuilder
     * @see IoFilterChainBuilder
     */
    public void setIoFilterChainBuilder(IoFilterChainBuilder ioFilterChainBuilder) {
        this.ioFilterChainBuilder = ioFilterChainBuilder;
    }

    protected IoFilterChainBuilder getIoFilterChainBuilder() {
        return ioFilterChainBuilder;
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy