/** * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You 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 org.apache.camel.component.quickfixj; import java.io.IOException; import java.io.InputStream; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Set; import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.atomic.AtomicBoolean; import javax.management.JMException; import javax.management.ObjectName; import org.apache.camel.support.ServiceSupport; import org.apache.camel.util.ObjectHelper; import org.quickfixj.jmx.JmxExporter; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import quickfix.Acceptor; import quickfix.Application; import quickfix.ConfigError; import quickfix.DefaultMessageFactory; import quickfix.DoNotSend; import quickfix.FieldConvertError; import quickfix.FieldNotFound; import quickfix.FileLogFactory; import quickfix.FileStoreFactory; import quickfix.IncorrectDataFormat; import quickfix.IncorrectTagValue; import quickfix.Initiator; import quickfix.JdbcLogFactory; import quickfix.JdbcSetting; import quickfix.JdbcStoreFactory; import quickfix.LogFactory; import quickfix.MemoryStoreFactory; import quickfix.Message; import quickfix.MessageFactory; import quickfix.MessageStoreFactory; import quickfix.RejectLogon; import quickfix.SLF4JLogFactory; import quickfix.ScreenLogFactory; import quickfix.Session; import quickfix.SessionFactory; import quickfix.SessionID; import quickfix.SessionSettings; import quickfix.SleepycatStoreFactory; import quickfix.SocketAcceptor; import quickfix.SocketInitiator; import quickfix.ThreadedSocketAcceptor; import quickfix.ThreadedSocketInitiator; import quickfix.UnsupportedMessageType; /** * This is a wrapper class that provided QuickFIX/J initialization capabilities * beyond those supported in the core QuickFIX/J distribution. * * Specifically, it infers dependencies on specific implementations of message * stores and logs. It also supports extended QuickFIX/J settings properties to * specify threading models, custom store and log implementations, etc. * * The wrapper will create an initiator or acceptor or both depending on the * roles of sessions described in the settings file. */ public class QuickfixjEngine extends ServiceSupport { public static final String DEFAULT_START_TIME = "00:00:00"; public static final String DEFAULT_END_TIME = "00:00:00"; public static final long DEFAULT_HEARTBTINT = 30; public static final String SETTING_THREAD_MODEL = "ThreadModel"; public static final String SETTING_USE_JMX = "UseJmx"; private static final Logger LOG = LoggerFactory.getLogger(QuickfixjEngine.class); private Acceptor acceptor; private Initiator initiator; private JmxExporter jmxExporter; private MessageStoreFactory messageStoreFactory; private LogFactory sessionLogFactory; private MessageFactory messageFactory; private final MessageCorrelator messageCorrelator = new MessageCorrelator(); private List<QuickfixjEventListener> eventListeners = new CopyOnWriteArrayList<QuickfixjEventListener>(); private final String uri; private ObjectName acceptorObjectName; private ObjectName initiatorObjectName; private final SessionSettings settings; private final AtomicBoolean initialized = new AtomicBoolean(false); private boolean lazy; public enum ThreadModel { ThreadPerConnector, ThreadPerSession; } /** * @deprecated Better make use of the {@link #QuickfixjEngine(String, String)} constructor * as the {@code forcedShutdown} paramater had/has no effect. */ @Deprecated public QuickfixjEngine(String uri, String settingsResourceName, boolean forcedShutdown) throws ConfigError, FieldConvertError, IOException, JMException { this(uri, settingsResourceName, forcedShutdown, null, null, null); } public QuickfixjEngine(String uri, String settingsResourceName) throws ConfigError, FieldConvertError, IOException, JMException { this(uri, settingsResourceName, null, null, null); } /** * @deprecated Better make use of the {@link #QuickfixjEngine(String, String, MessageStoreFactory, LogFactory, MessageFactory)} constructor * as the {@code forcedShutdown} paramater had/has no effect. */ @Deprecated public QuickfixjEngine(String uri, String settingsResourceName, boolean forcedShutdown, MessageStoreFactory messageStoreFactoryOverride, LogFactory sessionLogFactoryOverride, MessageFactory messageFactoryOverride) throws ConfigError, FieldConvertError, IOException, JMException { this(uri, loadSettings(settingsResourceName), forcedShutdown, messageStoreFactoryOverride, sessionLogFactoryOverride, messageFactoryOverride); } public QuickfixjEngine(String uri, String settingsResourceName, MessageStoreFactory messageStoreFactoryOverride, LogFactory sessionLogFactoryOverride, MessageFactory messageFactoryOverride) throws ConfigError, FieldConvertError, IOException, JMException { this(uri, loadSettings(settingsResourceName), messageStoreFactoryOverride, sessionLogFactoryOverride, messageFactoryOverride); } /** * @deprecated Better make use of the {@link #QuickfixjEngine(String, SessionSettings, MessageStoreFactory, LogFactory, MessageFactory)} constructor * as the {@code forcedShutdown} paramater had/has no effect. */ @Deprecated public QuickfixjEngine(String uri, SessionSettings settings, boolean forcedShutdown, MessageStoreFactory messageStoreFactoryOverride, LogFactory sessionLogFactoryOverride, MessageFactory messageFactoryOverride) throws ConfigError, FieldConvertError, IOException, JMException { this(uri, settings, messageStoreFactoryOverride, sessionLogFactoryOverride, messageFactoryOverride); } public QuickfixjEngine(String uri, SessionSettings settings, MessageStoreFactory messageStoreFactoryOverride, LogFactory sessionLogFactoryOverride, MessageFactory messageFactoryOverride) throws ConfigError, FieldConvertError, IOException, JMException { this(uri, settings, messageStoreFactoryOverride, sessionLogFactoryOverride, messageFactoryOverride, false); } public QuickfixjEngine(String uri, SessionSettings settings, MessageStoreFactory messageStoreFactoryOverride, LogFactory sessionLogFactoryOverride, MessageFactory messageFactoryOverride, boolean lazy) throws ConfigError, FieldConvertError, IOException, JMException { addEventListener(messageCorrelator); this.uri = uri; this.lazy = lazy; this.settings = settings; // overrides if (messageFactoryOverride != null) { messageFactory = messageFactoryOverride; } if (sessionLogFactoryOverride != null) { sessionLogFactory = sessionLogFactoryOverride; } if (messageStoreFactoryOverride != null) { messageStoreFactory = messageStoreFactoryOverride; } if (!lazy) { initializeEngine(); } } /** * Initializes the engine on demand. May be called immediately in constructor or when needed. * If initializing later, it should be started afterwards. */ void initializeEngine() throws ConfigError, FieldConvertError, JMException { if (messageFactory == null) { messageFactory = new DefaultMessageFactory(); } if (sessionLogFactory == null) { sessionLogFactory = inferLogFactory(settings); } if (messageStoreFactory == null) { messageStoreFactory = inferMessageStoreFactory(settings); } // Set default session schedule if not specified in configuration if (!settings.isSetting(Session.SETTING_START_TIME)) { settings.setString(Session.SETTING_START_TIME, DEFAULT_START_TIME); } if (!settings.isSetting(Session.SETTING_END_TIME)) { settings.setString(Session.SETTING_END_TIME, DEFAULT_END_TIME); } // Default heartbeat interval if (!settings.isSetting(Session.SETTING_HEARTBTINT)) { settings.setLong(Session.SETTING_HEARTBTINT, DEFAULT_HEARTBTINT); } // Allow specification of the QFJ threading model ThreadModel threadModel = ThreadModel.ThreadPerConnector; if (settings.isSetting(SETTING_THREAD_MODEL)) { threadModel = ThreadModel.valueOf(settings.getString(SETTING_THREAD_MODEL)); } if (settings.isSetting(SETTING_USE_JMX) && settings.getBool(SETTING_USE_JMX)) { LOG.info("Enabling JMX for QuickFIX/J"); jmxExporter = new JmxExporter(); } else { jmxExporter = null; } // From original component implementation... // To avoid this exception in OSGi platform // java.lang.NoClassDefFoundError: quickfix/fix41/MessageFactory ClassLoader ccl = Thread.currentThread().getContextClassLoader(); try { Thread.currentThread().setContextClassLoader(getClass().getClassLoader()); if (isConnectorRole(settings, SessionFactory.ACCEPTOR_CONNECTION_TYPE)) { acceptor = createAcceptor(new Dispatcher(), settings, messageStoreFactory, sessionLogFactory, messageFactory, threadModel); } else { acceptor = null; } if (isConnectorRole(settings, SessionFactory.INITIATOR_CONNECTION_TYPE)) { initiator = createInitiator(new Dispatcher(), settings, messageStoreFactory, sessionLogFactory, messageFactory, threadModel); } else { initiator = null; } if (acceptor == null && initiator == null) { throw new ConfigError("No connector role"); } } finally { Thread.currentThread().setContextClassLoader(ccl); } initialized.set(true); } static SessionSettings loadSettings(String settingsResourceName) throws ConfigError { InputStream inputStream = ObjectHelper.loadResourceAsStream(settingsResourceName); if (inputStream == null) { throw new IllegalArgumentException("Could not load " + settingsResourceName); } return new SessionSettings(inputStream); } @Override protected void doStart() throws Exception { if (acceptor != null) { acceptor.start(); if (jmxExporter != null) { acceptorObjectName = jmxExporter.register(acceptor); } } if (initiator != null) { initiator.start(); if (jmxExporter != null) { initiatorObjectName = jmxExporter.register(initiator); } } } @Override protected void doStop() throws Exception { if (acceptor != null) { acceptor.stop(); if (jmxExporter != null && acceptorObjectName != null) { jmxExporter.getMBeanServer().unregisterMBean(acceptorObjectName); } } if (initiator != null) { initiator.stop(); if (jmxExporter != null && initiatorObjectName != null) { jmxExporter.getMBeanServer().unregisterMBean(initiatorObjectName); } } } @Override protected void doShutdown() throws Exception { // also clear event listeners eventListeners.clear(); } private Initiator createInitiator(Application application, SessionSettings settings, MessageStoreFactory messageStoreFactory, LogFactory sessionLogFactory, MessageFactory messageFactory, ThreadModel threadModel) throws ConfigError { Initiator initiator; if (threadModel == ThreadModel.ThreadPerSession) { initiator = new ThreadedSocketInitiator(application, messageStoreFactory, settings, sessionLogFactory, messageFactory); } else if (threadModel == ThreadModel.ThreadPerConnector) { initiator = new SocketInitiator(application, messageStoreFactory, settings, sessionLogFactory, messageFactory); } else { throw new ConfigError("Unknown thread mode: " + threadModel); } return initiator; } private Acceptor createAcceptor(Application application, SessionSettings settings, MessageStoreFactory messageStoreFactory, LogFactory sessionLogFactory, MessageFactory messageFactory, ThreadModel threadModel) throws ConfigError { Acceptor acceptor; if (threadModel == ThreadModel.ThreadPerSession) { acceptor = new ThreadedSocketAcceptor(application, messageStoreFactory, settings, sessionLogFactory, messageFactory); } else if (threadModel == ThreadModel.ThreadPerConnector) { acceptor = new SocketAcceptor(application, messageStoreFactory, settings, sessionLogFactory, messageFactory); } else { throw new ConfigError("Unknown thread mode: " + threadModel); } return acceptor; } private MessageStoreFactory inferMessageStoreFactory(SessionSettings settings) throws ConfigError { Set<MessageStoreFactory> impliedMessageStoreFactories = new HashSet<MessageStoreFactory>(); isJdbcStore(settings, impliedMessageStoreFactories); isFileStore(settings, impliedMessageStoreFactories); isSleepycatStore(settings, impliedMessageStoreFactories); if (impliedMessageStoreFactories.size() > 1) { throw new ConfigError("Ambiguous message store implied in configuration."); } MessageStoreFactory messageStoreFactory; if (impliedMessageStoreFactories.size() == 1) { messageStoreFactory = impliedMessageStoreFactories.iterator().next(); } else { messageStoreFactory = new MemoryStoreFactory(); } LOG.info("Inferring message store factory: {}", messageStoreFactory.getClass().getName()); return messageStoreFactory; } private void isSleepycatStore(SessionSettings settings, Set<MessageStoreFactory> impliedMessageStoreFactories) { if (settings.isSetting(SleepycatStoreFactory.SETTING_SLEEPYCAT_DATABASE_DIR)) { impliedMessageStoreFactories.add(new SleepycatStoreFactory(settings)); } } private void isFileStore(SessionSettings settings, Set<MessageStoreFactory> impliedMessageStoreFactories) { if (settings.isSetting(FileStoreFactory.SETTING_FILE_STORE_PATH)) { impliedMessageStoreFactories.add(new FileStoreFactory(settings)); } } private void isJdbcStore(SessionSettings settings, Set<MessageStoreFactory> impliedMessageStoreFactories) { if (settings.isSetting(JdbcSetting.SETTING_JDBC_DRIVER) || settings.isSetting(JdbcSetting.SETTING_JDBC_DS_NAME)) { impliedMessageStoreFactories.add(new JdbcStoreFactory(settings)); } } private LogFactory inferLogFactory(SessionSettings settings) throws ConfigError { Set<LogFactory> impliedLogFactories = new HashSet<LogFactory>(); isFileLog(settings, impliedLogFactories); isScreenLog(settings, impliedLogFactories); isSL4JLog(settings, impliedLogFactories); isJdbcLog(settings, impliedLogFactories); if (impliedLogFactories.size() > 1) { throw new ConfigError("Ambiguous log factory implied in configuration"); } LogFactory sessionLogFactory; if (impliedLogFactories.size() == 1) { sessionLogFactory = impliedLogFactories.iterator().next(); } else { // Default sessionLogFactory = new ScreenLogFactory(settings); } LOG.info("Inferring log factory: {}", sessionLogFactory.getClass().getName()); return sessionLogFactory; } private void isScreenLog(SessionSettings settings, Set<LogFactory> impliedLogFactories) { if (settings.isSetting(ScreenLogFactory.SETTING_LOG_EVENTS) || settings.isSetting(ScreenLogFactory.SETTING_LOG_INCOMING) || settings.isSetting(ScreenLogFactory.SETTING_LOG_OUTGOING)) { impliedLogFactories.add(new ScreenLogFactory(settings)); } } private void isFileLog(SessionSettings settings, Set<LogFactory> impliedLogFactories) { if (settings.isSetting(FileLogFactory.SETTING_FILE_LOG_PATH)) { impliedLogFactories.add(new FileLogFactory(settings)); } } private void isJdbcLog(SessionSettings settings, Set<LogFactory> impliedLogFactories) { if ((settings.isSetting(JdbcSetting.SETTING_JDBC_DRIVER) || settings.isSetting(JdbcSetting.SETTING_JDBC_DS_NAME)) && settings.isSetting(JdbcSetting.SETTING_LOG_EVENT_TABLE)) { impliedLogFactories.add(new JdbcLogFactory(settings)); } } private void isSL4JLog(SessionSettings settings, Set<LogFactory> impliedLogFactories) { for (Object key : settings.getDefaultProperties().keySet()) { if (key.toString().startsWith("SLF4J")) { impliedLogFactories.add(new SLF4JLogFactory(settings)); return; } } } private boolean isConnectorRole(SessionSettings settings, String connectorRole) throws ConfigError { boolean hasRole = false; Iterator<SessionID> sessionIdItr = settings.sectionIterator(); while (sessionIdItr.hasNext()) { try { if (connectorRole.equals(settings.getString(sessionIdItr.next(), SessionFactory.SETTING_CONNECTION_TYPE))) { hasRole = true; break; } } catch (FieldConvertError e) { throw new ConfigError(e); } } return hasRole; } public void addEventListener(QuickfixjEventListener listener) { eventListeners.add(listener); } public void removeEventListener(QuickfixjEventListener listener) { eventListeners.remove(listener); } private class Dispatcher implements Application { @Override public void fromAdmin(Message message, SessionID sessionID) throws FieldNotFound, IncorrectDataFormat, IncorrectTagValue, RejectLogon { try { dispatch(QuickfixjEventCategory.AdminMessageReceived, sessionID, message); } catch (RuntimeException e) { throw e; } catch (Exception e) { rethrowIfType(e, FieldNotFound.class); rethrowIfType(e, IncorrectDataFormat.class); rethrowIfType(e, IncorrectTagValue.class); rethrowIfType(e, RejectLogon.class); throw new DispatcherException(e); } } @Override public void fromApp(Message message, SessionID sessionID) throws FieldNotFound, IncorrectDataFormat, IncorrectTagValue, UnsupportedMessageType { try { dispatch(QuickfixjEventCategory.AppMessageReceived, sessionID, message); } catch (RuntimeException e) { throw e; } catch (Exception e) { rethrowIfType(e, FieldNotFound.class); rethrowIfType(e, IncorrectDataFormat.class); rethrowIfType(e, IncorrectTagValue.class); rethrowIfType(e, UnsupportedMessageType.class); throw new DispatcherException(e); } } @Override public void onCreate(SessionID sessionID) { try { dispatch(QuickfixjEventCategory.SessionCreated, sessionID, null); } catch (Exception e) { throw new DispatcherException(e); } } @Override public void onLogon(SessionID sessionID) { try { dispatch(QuickfixjEventCategory.SessionLogon, sessionID, null); } catch (Exception e) { throw new DispatcherException(e); } } @Override public void onLogout(SessionID sessionID) { try { dispatch(QuickfixjEventCategory.SessionLogoff, sessionID, null); } catch (Exception e) { throw new DispatcherException(e); } } @Override public void toAdmin(Message message, SessionID sessionID) { try { dispatch(QuickfixjEventCategory.AdminMessageSent, sessionID, message); } catch (Exception e) { throw new DispatcherException(e); } } @Override public void toApp(Message message, SessionID sessionID) throws DoNotSend { try { dispatch(QuickfixjEventCategory.AppMessageSent, sessionID, message); } catch (Exception e) { throw new DispatcherException(e); } } private <T extends Exception> void rethrowIfType(Exception e, Class<T> exceptionClass) throws T { if (e.getClass() == exceptionClass) { throw exceptionClass.cast(e); } } private void dispatch(QuickfixjEventCategory quickfixjEventCategory, SessionID sessionID, Message message) throws Exception { LOG.debug("FIX event dispatched: {} {}", quickfixjEventCategory, message != null ? message : ""); for (QuickfixjEventListener listener : eventListeners) { // Exceptions propagate back to the FIX engine so sequence numbers can be adjusted listener.onEvent(quickfixjEventCategory, sessionID, message); } } private class DispatcherException extends RuntimeException { private static final long serialVersionUID = 1L; DispatcherException(Throwable cause) { super(cause); } } } public String getUri() { return uri; } public MessageCorrelator getMessageCorrelator() { return messageCorrelator; } public boolean isInitialized() { return this.initialized.get(); } public boolean isLazy() { return this.lazy; } // For Testing Initiator getInitiator() { return initiator; } // For Testing Acceptor getAcceptor() { return acceptor; } // For Testing MessageStoreFactory getMessageStoreFactory() { return messageStoreFactory; } // For Testing LogFactory getLogFactory() { return sessionLogFactory; } // For Testing MessageFactory getMessageFactory() { return messageFactory; } }