/* * 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.synapse.transport.fix; import org.apache.axis2.AxisFault; import org.apache.axis2.description.AxisService; import org.apache.axis2.description.Parameter; import org.apache.axis2.transport.base.BaseUtils; import org.apache.axis2.transport.base.threads.WorkerPool; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.quickfixj.jmx.JmxExporter; import quickfix.*; import javax.management.JMException; import java.io.IOException; import java.io.InputStream; import java.net.MalformedURLException; import java.net.URL; import java.util.HashMap; import java.util.Hashtable; import java.util.Map; /** * The FIXSessionFactory is responsible for creating and managing FIX sessions. A FIX session can be * initiated in one of two modes, namely the acceptor mode and the initiator mode. FIX sessions * requested by the transport listener at service deployment are created in acceptor mode. When * the transport sender is about to send a FIX message it will check whether a valid FIX session exists. * If not it will request the FIXSessionFactory to create a new session in the initiator mode. * <p/> * To create a new FIX session (in either mode) the FIXSessionFactory has to create a LogFactory (nullable), * and a MessageStoreFactroy. By default this implementation attempts to pass null as the LogFactory and a * MemoryStoreFactory as the MessageStoreFactory. These can be configured in the services.xml as follows. * <p/> * <parameter name="transport.fix.AcceptorLogFactory">file</parameter> * (acceptable values: console, file, jdbc) * <p/> * <parameter name="transport.fix.AcceptorMessageStore">file</parameter> * (acceptable values: file, jdbc, memory, sleepycat) * <p/> * The configuraion details related to these factories has to be specified in the FIX configuration file * as requested by the Quickfix/J API. */ public class FIXSessionFactory { /** A Map Containing all the FIX Acceptors created by this factory, keyed by the service name */ private Map<String,Acceptor> acceptorStore; /** A Map containing all the FIX Initiators created by this factory, keyed by FIX EPR */ private Map<String,Initiator> initiatorStore; /** A Map containing all the FIX applications created for initiators, keyed by FIX EPR */ private Map<String, Application> applicationStore; /** An ApplicationFactory handles creating FIX Applications (FIXIncomingMessageHandler Objects) */ private static FIXApplicationFactory applicationFactory = null; private WorkerPool listenerThreadPool; private WorkerPool senderThreadPool; private Log log; private static FIXSessionFactory INSTANCE = new FIXSessionFactory(); public static FIXSessionFactory getInstance(FIXApplicationFactory af) { if (applicationFactory == null) { applicationFactory = af; } return INSTANCE; } private FIXSessionFactory() { this.log = LogFactory.getLog(this.getClass()); this.acceptorStore = new HashMap<String,Acceptor>(); this.initiatorStore = new HashMap<String, Initiator>(); this.applicationStore = new HashMap<String, Application>(); this.listenerThreadPool = null; this.senderThreadPool = null; } /** * Get the FIX configuration settings and initialize a new FIX session for the specified * service. Create an Acceptor and a new FIX Application. Put the Acceptor into the * acceptorStore keyed by the service name and start it. * * @param service the AxisService * @return true if the acceptor is successfully initialized and false otherwise * @throws AxisFault if the acceptor cannot be created */ public boolean createFIXAcceptor(AxisService service) throws AxisFault { //Try to get an InputStream to the FIX configuration file InputStream fixConfigStream = getFIXConfigAsStream(service, true); if (fixConfigStream != null) { try { if (log.isDebugEnabled()) { log.debug ("Initializing a new FIX session for the service " + service.getName()); } SessionSettings settings = new SessionSettings(fixConfigStream); MessageStoreFactory storeFactory = getMessageStoreFactory(service, settings, true); MessageFactory messageFactory = new DefaultMessageFactory(); quickfix.LogFactory logFactory = getLogFactory(service, settings, true); //Get a new FIX Application Application messageHandler = applicationFactory.getFIXApplication(service, listenerThreadPool, true); //Create a new FIX Acceptor Acceptor acceptor = new SocketAcceptor( messageHandler, storeFactory, settings, logFactory, messageFactory); acceptor.start(); initJMX(acceptor, service.getName()); acceptorStore.put(service.getName(),acceptor); return true; } catch (ConfigError e) { String msg = "Error in the specified FIX configuration. Unable to initialize a " + "FIX session for the service " + service.getName(); log.error(msg, e); throw new AxisFault(msg, e); } } else { return false; } } /** * Extract the parameters embedded in the given EPR and initialize a new FIX session. * Create a new FIX initiator and a new FIX Application.Put the initiator into the * initiatorStore keyed by the EPR and start the initiator. * * @param fixEPR the EPR to send FIX messages * @param service the AxisService * @param sessionID the SessionID of the session created * @throws org.apache.axis2.AxisFault Exception thrown */ public void createFIXInitiator(String fixEPR, AxisService service, SessionID sessionID) throws AxisFault { if (log.isDebugEnabled()) { log.debug("Initializing a new FIX initiator for the service " + service.getName()); } SessionSettings settings; InputStream fixConfigStream = getFIXConfigAsStream(service, false); if (fixConfigStream == null) { settings = new SessionSettings(); settings.setLong(sessionID, FIXConstants.HEART_BY_INT, FIXConstants.DEFAULT_HEART_BT_INT_VALUE); settings.setString(sessionID, FIXConstants.START_TIME, FIXConstants.DEFAULT_START_TIME_VALUE); settings.setString(sessionID, FIXConstants.END_TIME, FIXConstants.DEFAULT_END_TIME_VALUE); } else { try { settings = new SessionSettings(fixConfigStream); } catch (ConfigError e) { throw new AxisFault("Error in the specified FIX configuration for the initiator. " + "Unable to initialize a FIX session for the service " + service.getName(), e); } } Hashtable<String,String> properties = BaseUtils.getEPRProperties(fixEPR); for (Map.Entry<String,String> entry : properties.entrySet()) { settings.setString(sessionID, entry.getKey(), entry.getValue()); } String[] socketAddressElements = FIXUtils.getSocketAddressElements(fixEPR); settings.setString(sessionID, FIXConstants.CONNECTION_TYPE, FIXConstants.FIX_INITIATOR); settings.setString(sessionID, FIXConstants.SOCKET_CONNECT_HOST, socketAddressElements[0]); settings.setString(sessionID, FIXConstants.SOCKET_CONNECT_PORT, socketAddressElements[1]); quickfix.LogFactory logFactory = getLogFactory(service, settings, false); MessageStoreFactory storeFactory = getMessageStoreFactory(service, settings, false); MessageFactory messageFactory = new DefaultMessageFactory(); //Get a new FIX application Application messageHandler = applicationFactory.getFIXApplication(service, senderThreadPool, false); try { //Create a new FIX initiator Initiator initiator = new SocketInitiator( messageHandler, storeFactory, settings, logFactory, messageFactory); initiator.start(); initJMX(initiator, service.getName()); initiatorStore.put(fixEPR, initiator); applicationStore.put(fixEPR, messageHandler); FIXIncomingMessageHandler fixMessageHandler = (FIXIncomingMessageHandler) messageHandler; log.info("Waiting for logon procedure to complete..."); fixMessageHandler.acquire(); } catch (ConfigError e) { throw new AxisFault("Error in the specified FIX configuration for the initiator. Unable " + "to initialize a FIX initiator.", e); } catch (InterruptedException ignore) { } } public boolean createFIXInitiator(AxisService service) throws AxisFault { InputStream fixConfigStream = getFIXConfigAsStream(service, false); if (fixConfigStream != null) { if (log.isDebugEnabled()) { log.debug("Attempting to initialize a new FIX initiator " + "for the service " + service.getName()); } try { SessionSettings settings = new SessionSettings(fixConfigStream); /*Stop and Clean-up if there is already existing initiator with the same key */ String[] existingEPRs = FIXUtils.getEPRs(settings); for (String epr : existingEPRs) { if (initiatorStore.get(epr) != null) { initiatorStore.get(epr).stop(); initiatorStore.remove(epr); } if (applicationStore.get(epr) != null) { applicationStore.remove(epr); } } MessageStoreFactory storeFactory = getMessageStoreFactory(service, settings, false); MessageFactory messageFactory = new DefaultMessageFactory(); quickfix.LogFactory logFactory = getLogFactory(service, settings, false); //Get a new FIX Application Application messageHandler = applicationFactory.getFIXApplication(service, senderThreadPool, false); Initiator initiator = new SocketInitiator( messageHandler, storeFactory, settings, logFactory, messageFactory); initiator.start(); initJMX(initiator, service.getName()); String[] EPRs = FIXUtils.getEPRs(settings); for (String EPR : EPRs) { initiatorStore.put(EPR, initiator); applicationStore.put(EPR, messageHandler); } return true; } catch (FieldConvertError e) { String msg = "FIX configuration file for the initiator session of the service " + service.getName() + " is either incomplete or invalid." + " Not creating the initiator session at this stage."; log.error(msg, e); throw new AxisFault(msg, e); } catch (ConfigError e) { String msg = "FIX configuration file for the initiator session of the service " + service.getName() + " is either incomplete or invalid." + " Not creating the initiator session at this stage."; log.error(msg, e); throw new AxisFault(msg, e); } } else { // FIX initiator session is not configured // It could be intentional - So not an error (we don't need initiators at all times) log.info("The " + FIXConstants.FIX_INITIATOR_CONFIG_URL_PARAM + " parameter is " + "not specified. Unable to initialize the initiator session at this stage."); } return false; } /** * Get the FIX Acceptor for the specified service from the sessionStore Map and * stop it. Then remove the Acceptor from the Map. * * @param service the AxisService */ public void disposeFIXAcceptor(AxisService service) { if (log.isDebugEnabled()) { log.debug("Stopping the FIX acceptor for the service " + service.getName()); } //Get the Acceptor for the service Acceptor acceptor = acceptorStore.get(service.getName()); if (acceptor != null) { //Stop the Acceptor acceptor.stop(); log.info("FIX session for service " + service.getName() + " terminated..."); //Remove the Acceptor from the store acceptorStore.remove(service.getName()); } } /** * Stops all the FIX initiators created so far and cleans up all the mappings * related to them */ public void disposeFIXInitiators() { boolean debugEnabled = log.isDebugEnabled(); for (String key : initiatorStore.keySet()) { initiatorStore.get(key).stop(); if (debugEnabled) { log.debug("FIX initiator to the EPR " + key + " stopped"); } } initiatorStore.clear(); applicationStore.clear(); } /** * Returns an array of Strings representing EPRs for the specified service * * @param serviceName the name of the service * @param ip the IP address of the host * @return an array of EPRs for the specified service */ public String[] getServiceEPRs(String serviceName, String ip) { if (log.isDebugEnabled()) { log.debug("Getting EPRs for the service " + serviceName); } //Get the acceptpr for the specified service SocketAcceptor acceptor = (SocketAcceptor) acceptorStore.get(serviceName); if (acceptor != null) { return FIXUtils.generateEPRs(acceptor, serviceName, ip); } else { return new String[]{}; } } /** * Finds a FIX Acceptor for the specified service from the acceptorStore * * @param serviceName the name of the AxisService * @return a FIX Acceptor for the service */ public Acceptor getAcceptor(String serviceName) { return acceptorStore.get(serviceName); } /** * Finds a FIX initiator for the specified EPR from the initiatorStore * * @param fixEPR a valid FIX EPR * @return a FIX initiator for the EPR */ public Initiator getInitiator(String fixEPR) { return initiatorStore.get(fixEPR); } /** * Get the FIX configuration URL from the services.xml. * * @param service the AxisService * @param acceptor boolean value indicating the FIX application type * @return an InputStream to the FIX configuration file/resource */ private InputStream getFIXConfigAsStream(AxisService service, boolean acceptor) { InputStream fixConfigStream = null; Parameter fixConfigURLParam; if (acceptor) { fixConfigURLParam = service.getParameter(FIXConstants.FIX_ACCEPTOR_CONFIG_URL_PARAM); } else { fixConfigURLParam = service.getParameter(FIXConstants.FIX_INITIATOR_CONFIG_URL_PARAM); } if (fixConfigURLParam != null) { String fixConfigURLValue = fixConfigURLParam.getValue().toString(); try { URL url = new URL(fixConfigURLValue); fixConfigStream = url.openStream(); } catch (MalformedURLException e) { log.error("The FIX configuration URL " + fixConfigURLValue + " is" + " malformed.", e); } catch (IOException e) { log.error("Error while reading from the URL " + fixConfigURLValue, e); } } else { log.info("FIX configuration URL is not specified for the service " + service.getName()); } return fixConfigStream; } /** * Creates a Quickfix LogFactory object for logging as specified in the services.xml and * the FIX configuration file. Default is null. * * @param service the AxisService * @param settings SessionSettings to be used with the service * @param acceptor a boolean value indicating the type of the FIX application * @return a LogFactory for the FIX application */ private quickfix.LogFactory getLogFactory(AxisService service, SessionSettings settings, boolean acceptor) { quickfix.LogFactory logFactory = null; Parameter fixLogMethod; //Read the parameter from the services.xml if (acceptor) { fixLogMethod = service.getParameter(FIXConstants.FIX_ACCEPTOR_LOGGER_PARAM); } else { fixLogMethod = service.getParameter(FIXConstants.FIX_INITIATOR_LOGGER_PARAM); } if (fixLogMethod != null) { String method = fixLogMethod.getValue().toString(); log.info("FIX logging method = " + method); if (FIXConstants.FILE_BASED_MESSAGE_LOGGING.equals(method)) { logFactory = new FileLogFactory(settings); } else if (FIXConstants.JDBC_BASED_MESSAGE_LOGGING.equals(method)) { logFactory = new JdbcLogFactory(settings); } else if (FIXConstants.CONSOLE_BASED_MESSAGE_LOGGING.equals(method)) { logFactory = new ScreenLogFactory(); } else { log.warn("Invalid acceptor log method " + method + ". Using defaults."); } } return logFactory; } /** * Creates a Quickfix MessageStoreFactory for storing FIX messages as specified in the services.xml * and the FIX configuration file. Default is FileStoreFactory. * * @param service the AxisService * @param settings SessionSettings to be used with the service * @param acceptor a boolean value indicating the type of the FIX application * @return a MessageStoreFactory for the FIX application */ private MessageStoreFactory getMessageStoreFactory(AxisService service, SessionSettings settings, boolean acceptor) { MessageStoreFactory storeFactory = new MemoryStoreFactory(); Parameter msgLogMethod; //Read the parameter from the services.xml if (acceptor) { msgLogMethod = service.getParameter(FIXConstants.FIX_ACCEPTOR_MESSAGE_STORE_PARAM); } else { msgLogMethod = service.getParameter(FIXConstants.FIX_INITIATOR_MESSAGE_STORE_PARAM); } if (msgLogMethod != null) { String method = msgLogMethod.getValue().toString(); log.info("FIX message logging method = " + method); if (FIXConstants.JDBC_BASED_MESSAGE_STORE.equals(method)) { storeFactory = new JdbcStoreFactory(settings); } else if (FIXConstants.SLEEPYCAT_BASED_MESSAGE_STORE.equals(method)) { storeFactory = new SleepycatStoreFactory(settings); } else if (FIXConstants.FILE_BASED_MESSAGE_STORE.equals(method)) { storeFactory = new FileStoreFactory(settings); } else if (!FIXConstants.MEMORY_BASED_MESSAGE_STORE.equals(method)) { log.warn("Invalid message store " + method + ". Using defaults."); } } return storeFactory; } public Application getApplication(String fixEPR) { Application app = applicationStore.get(fixEPR); if (app == null) { for (String epr : applicationStore.keySet()) { if (FIXUtils.compareURLs(epr, fixEPR)) { app = applicationStore.get(epr); applicationStore.remove(epr); applicationStore.put(fixEPR, app); break; } } } return app; } public void setListenerThreadPool(WorkerPool listenerThreadPool) { this.listenerThreadPool = listenerThreadPool; } public void setSenderThreadPool(WorkerPool senderThreadPool) { this.senderThreadPool = senderThreadPool; } private void initJMX(Connector connector, String service) { try { JmxExporter jmxExporter = new JmxExporter(); jmxExporter.setRegistrationBehavior(JmxExporter.REGISTRATION_IGNORE_EXISTING); jmxExporter.export(connector); } catch (JMException e) { log.error("Error while initializing JMX support for the service: " + service, e); } } }