/* * 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.context.MessageContext; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import quickfix.Message; import quickfix.Session; import quickfix.SessionID; import quickfix.SessionNotFound; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; /** * FIXOutgoingMessageHandler makes sure that messages are delivered in the order they were received by * a FIX acceptor. In case the message arrived over a different transport still this class will try to * put the messages in correct order based on the counter value of the message. */ public class FIXOutgoingMessageHandler { private static final Log log = LogFactory.getLog(FIXOutgoingMessageHandler.class); private Map<String, Integer> countersMap; private Map<String, Map<Integer,Object[]>> messagesMap; private FIXSessionFactory sessionFactory; public FIXOutgoingMessageHandler() { countersMap = new ConcurrentHashMap<String, Integer>(); messagesMap = new ConcurrentHashMap<String, Map<Integer,Object[]>>(); } public void setSessionFactory(FIXSessionFactory sessionFactory) { this.sessionFactory = sessionFactory; } /** * Performs the actual send operation on the message. Tries to send the messages in the order they * arrived over the FIX transport * * @param message the FIX message to be sent * @param targetSession ID of the target FIXSession * @param sourceSession String that uniquely identifies the incoming session * @param counter application level sequence number of the message * @param msgCtx Axis2 MessageContext for the outgoing message * @param targetEPR the target EPR to forward the message * * @throws SessionNotFound on error */ public synchronized void sendMessage(Message message, SessionID targetSession, String sourceSession, int counter, MessageContext msgCtx, String targetEPR) throws SessionNotFound { boolean ignoreOrder = "true".equals(msgCtx.getProperty(FIXConstants.FIX_IGNORE_ORDER)); if (sourceSession != null && counter != -1 && !ignoreOrder) { int expectedValue; if (countersMap.containsKey(sourceSession)) { expectedValue = countersMap.get(sourceSession); } else { //create new entries in the respective Maps //counter starts at 1 countersMap.put(sourceSession, 1); messagesMap.put(sourceSession, new ConcurrentHashMap<Integer,Object[]>()); expectedValue = 1; } if (expectedValue == counter) { sendToTarget(msgCtx, targetEPR, message, targetSession); if (FIXConstants.DEFAULT_COUNTER_UPPER_LIMIT == expectedValue) { if (log.isDebugEnabled()) { log.debug("Outgoing request counter rolled over for the session: " + sourceSession + " (from " + expectedValue + ")"); } expectedValue = 1; } countersMap.put(sourceSession, ++expectedValue); sendQueuedMessages(expectedValue, sourceSession); } else { if (log.isDebugEnabled()) { log.debug("Source session: " + sourceSession + " - Expected sequence number (" + expectedValue + ") does not match with the actual sequence number (" + counter + "). Holding the message back for later delivery."); } //save the message to be sent later... Map<Integer,Object[]> messages = messagesMap.get(sourceSession); Object[] obj = new Object[] { message, targetSession, msgCtx, targetEPR } ; messages.put(counter, obj); } } else { //insufficient information to send the messages in order... // send it right away... sendToTarget(msgCtx, targetEPR, message, targetSession); } } /** * Sends the FIX message to the given target session. If MessageContext and the target EPR * are not null then save the outgoing MessageContext in the FIX application to handle the * response. * * @param msgCtx the Axis2 MessageContext of the outgoing message * @param targetEPR the target EPR to send the message * @param message the FIX message * @param sessionID the ID of the target FIX session * * @throws SessionNotFound on error */ private void sendToTarget(MessageContext msgCtx, String targetEPR, Message message, SessionID sessionID) throws SessionNotFound { if (msgCtx != null && targetEPR != null) { FIXIncomingMessageHandler messageHandler = (FIXIncomingMessageHandler) sessionFactory. getApplication(targetEPR); if (messageHandler != null) { messageHandler.setOutgoingMessageContext(msgCtx); } } Session.sendToTarget(message, sessionID); } /** * Sends any messages in the queues. Maintains the order of the messages. * * @param expectedValue expected counter value * @param session source FIX session * * @throws SessionNotFound on error */ private void sendQueuedMessages(int expectedValue, String session) throws SessionNotFound { Map<Integer, Object[]> messages = messagesMap.get(session); Object[] obj = messages.get(expectedValue); while (obj != null) { Message message = (Message) obj[0]; SessionID sessionID = (SessionID) obj[1]; MessageContext msgCtx = null; String targetEPR = null; if (obj[2] != null) { msgCtx = (MessageContext) obj[2]; targetEPR = (String) obj[3]; } if (log.isDebugEnabled()) { log.debug("Source session: " + session + " - Sending the previously queued message " + "with the sequence number: " + expectedValue); } sendToTarget(msgCtx, targetEPR, message, sessionID); messages.remove(expectedValue); if (FIXConstants.DEFAULT_COUNTER_UPPER_LIMIT == expectedValue) { if (log.isDebugEnabled()) { log.debug("Outgoing request counter rolled over for the session: " + session + " (from " + expectedValue + ")"); } expectedValue = 1; } obj = messages.get(++expectedValue); } messagesMap.put(session, messages); countersMap.put(session, expectedValue); } public void cleanUpMessages(String session) { if (countersMap.containsKey(session)) { int expectedValue = countersMap.get(session); Map<Integer, Object[]> messages = messagesMap.get(session); while (!messages.isEmpty()) { Object[] obj = messages.get(expectedValue); if (obj != null) { Message message = (Message) obj[0]; SessionID sessionID = (SessionID) obj[1]; if (log.isDebugEnabled()) { log.debug("Source session: " + session + " - Flushing the previously queued " + "message with the sequence number: " + expectedValue); } try { Session.sendToTarget(message, sessionID); } catch (SessionNotFound ignore) { } messages.remove(expectedValue); } expectedValue++; } messagesMap.remove(session); countersMap.remove(session); } } }