/*
* 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.Constants;
import org.apache.axis2.wsdl.WSDLConstants;
import org.apache.axis2.context.ConfigurationContext;
import org.apache.axis2.context.MessageContext;
import org.apache.axis2.description.AxisOperation;
import org.apache.axis2.description.AxisService;
import org.apache.axis2.description.Parameter;
import org.apache.axis2.transport.base.AbstractTransportListener;
import org.apache.axis2.transport.base.AbstractTransportSender;
import org.apache.axis2.transport.base.BaseConstants;
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 quickfix.*;
import quickfix.field.MsgSeqNum;
import quickfix.field.MsgType;
import quickfix.field.SenderCompID;
import quickfix.field.TargetCompID;
import javax.xml.namespace.QName;
import java.util.Map;
import java.util.Queue;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.Semaphore;
import java.util.concurrent.atomic.AtomicInteger;
/**
* FIXIncomingMessageHandler is responsible for handling all incoming FIX messages. This is where the
* Quickfix/J engine meets Synapse core. Admin level FIX messages are handled by Quickfix/J itself.
* All the application level messages are handed over to the Synapse core.
*/
public class FIXIncomingMessageHandler implements Application {
private ConfigurationContext cfgCtx;
/** A thread pool used to process incoming FIX messages */
private WorkerPool workerPool;
/** AxisService to which this FIX application is bound to */
private AxisService service;
private Log log;
/** A boolean value indicating the type of the FIX application */
private boolean acceptor;
/** A Map of counters with one counter per session */
private Map<SessionID, AtomicInteger> countersMap;
private Queue<MessageContext> outgoingMessages;
private boolean allNewApproach = true;
private boolean dropExtraResponses = false;
private Semaphore semaphore;
private SessionEventHandler eventHandler;
public FIXIncomingMessageHandler(ConfigurationContext cfgCtx, WorkerPool workerPool,
AxisService service, boolean acceptor) {
this.cfgCtx = cfgCtx;
this.workerPool = workerPool;
this.service = service;
this.log = LogFactory.getLog(this.getClass());
this.acceptor = acceptor;
countersMap = new ConcurrentHashMap<SessionID, AtomicInteger>();
outgoingMessages = new LinkedBlockingQueue<MessageContext>();
semaphore = new Semaphore(0);
getResponseHandlingApproach();
Parameter eventHandlerParam;
if (acceptor) {
eventHandlerParam = service.getParameter(FIXConstants.FIX_ACCEPTOR_EVENT_HANDLER);
} else {
eventHandlerParam = service.getParameter(FIXConstants.FIX_INITIATOR_EVENT_HANDLER);
}
if (eventHandlerParam != null && eventHandlerParam.getValue() != null &&
!"".equals(eventHandlerParam.getValue())) {
try {
Class clazz = getClass().getClassLoader().loadClass(
(String) eventHandlerParam.getValue());
eventHandler = (SessionEventHandler) clazz.newInstance();
} catch (ClassNotFoundException e) {
log.error("Unable to find the session event handler class: " +
eventHandlerParam.getValue(), e);
} catch (Exception e) {
log.error("Error while initializing the session event handler class: " +
eventHandlerParam.getValue(), e);
}
}
}
private void getResponseHandlingApproach() {
Parameter param = service.getParameter(FIXConstants.FIX_RESPONSE_HANDLER_APPROACH);
if (param != null && "false".equals(param.getValue().toString())) {
allNewApproach = false;
}
Parameter dropResponsesParam = service.getParameter(FIXConstants.FIX_DROP_EXTRA_RESPONSES);
if (dropResponsesParam != null && "true".equals(dropResponsesParam.getValue().toString())) {
dropExtraResponses = true;
}
}
public void setOutgoingMessageContext(MessageContext msgCtx) {
if (!allNewApproach) {
outgoingMessages.offer(msgCtx);
}
}
public void acquire() throws InterruptedException {
semaphore.acquire();
}
private void handleException(String msg, Exception e) {
log.error(msg, e);
throw new AxisFIXException(msg, e);
}
/**
* This method is called when quickfix creates a new session. A session
* comes into and remains in existence for the life of the application.
* Sessions exist whether or not a counter party is connected to it. As soon
* as a session is created, the application can begin sending messages to it. If no one
* is logged on, the messages will be sent at the time a connection is
* established with the counter party.
*
* @param sessionID QuickFIX session ID
*/
public void onCreate(SessionID sessionID) {
log.info("New FIX session created: " + sessionID.toString());
if (eventHandler != null) {
eventHandler.onCreate(sessionID);
}
}
/**
* This callback notifies when a valid logon has been established with a
* counter party. This is called when a connection has been established and
* the FIX logon process has completed with both parties exchanging valid
* logon messages.
*
* @param sessionID QuickFIX session ID
*/
public void onLogon(SessionID sessionID) {
if (!countersMap.containsKey(sessionID)) {
countersMap.put(sessionID, new AtomicInteger(0));
}
log.info("FIX session logged on: " + sessionID.toString());
semaphore.release();
if (eventHandler != null) {
eventHandler.onLogon(sessionID);
}
}
/**
* This callback notifies when a FIX session is no longer online. This
* could happen during a normal logout exchange or because of a forced
* termination or a loss of network connection.
*
* @param sessionID QuickFIX session ID
*/
public void onLogout(SessionID sessionID) {
FIXTransportSender trpSender = (FIXTransportSender) cfgCtx.getAxisConfiguration().
getTransportOut(FIXConstants.TRANSPORT_NAME).getSender();
trpSender.logOutIncomingSession(sessionID);
countersMap.remove(sessionID);
log.info("FIX session logged out: " + sessionID.toString());
if (eventHandler != null) {
eventHandler.onLogout(sessionID);
}
}
/**
* This callback provides Synapse with a peek at the administrative messages
* that are being sent from your FIX engine to the counter party. This is
* normally not useful for an application however it is provided for any
* logging one may wish to do.
*
* @param message QuickFIX message
* @param sessionID QuickFIX session ID
*/
public void toAdmin(Message message, SessionID sessionID) {
if (log.isDebugEnabled()) {
StringBuffer sb = new StringBuffer();
try {
sb.append("Sending admin level FIX message to ").append(message.getHeader().getField(new TargetCompID()).getValue());
sb.append("\nMessage Type: ").append(message.getHeader().getField(new MsgType()).getValue());
sb.append("\nMessage Sequence Number: ").append(message.getHeader().getField(new MsgSeqNum()).getValue());
sb.append("\nSender ID: ").append(message.getHeader().getField(new SenderCompID()).getValue());
} catch (FieldNotFound e) {
sb.append("Sending admin level FIX message...");
log.warn("One or more required fields are not found in the response message", e);
}
log.debug(sb.toString());
if (log.isTraceEnabled()) {
log.trace("Message: " + message.toString());
}
}
if (eventHandler != null) {
eventHandler.toAdmin(message, sessionID);
}
}
/**
* This callback notifies when an administrative message is sent from a
* counterparty to the FIX engine.
*
* @param message QuickFIX message
* @param sessionID QuickFIX session ID
* @throws FieldNotFound
* @throws IncorrectDataFormat
* @throws IncorrectTagValue
* @throws RejectLogon causes a logon reject
*/
public void fromAdmin(Message message, SessionID sessionID) throws FieldNotFound,
IncorrectDataFormat, IncorrectTagValue, RejectLogon {
if (log.isDebugEnabled()) {
StringBuffer sb = new StringBuffer();
sb.append("Received admin level FIX message from ").append(message.getHeader().getField(new SenderCompID()).getValue());
sb.append("\nMessage Type: ").append(message.getHeader().getField(new MsgType()).getValue());
sb.append("\nMessage Sequence Number: ").append(message.getHeader().getField(new MsgSeqNum()).getValue());
sb.append("\nReceiver ID: ").append(message.getHeader().getField(new TargetCompID()).getValue());
log.debug(sb.toString());
if (log.isTraceEnabled()) {
log.trace("Message: " + message.toString());
}
}
if (eventHandler != null) {
eventHandler.fromAdmin(message, sessionID);
}
}
/**
* This is a callback for application messages that are being sent to a
* counter party.
*
* @param message QuickFIX message
* @param sessionID QuickFIX session ID
* @throws DoNotSend This exception aborts message transmission
*/
public void toApp(Message message, SessionID sessionID) throws DoNotSend {
if (log.isDebugEnabled()) {
StringBuffer sb = new StringBuffer();
try {
sb.append("Sending application level FIX message to ").append(message.getHeader().getField(new TargetCompID()).getValue());
sb.append("\nMessage Type: ").append(message.getHeader().getField(new MsgType()).getValue());
sb.append("\nMessage Sequence Number: ").append(message.getHeader().getField(new MsgSeqNum()).getValue());
sb.append("\nSender ID: ").append(message.getHeader().getField(new SenderCompID()).getValue());
} catch (FieldNotFound e) {
sb.append("Sending application level FIX message...");
log.warn("One or more required fields are not found in the response message", e);
}
log.debug(sb.toString());
if (log.isTraceEnabled()) {
log.trace("Message: " + message.toString());
}
}
if (eventHandler != null) {
eventHandler.toApp(message, sessionID);
}
}
/**
* This callback receives messages for the application. This is one of the
* core entry points for the FIX application. Every application level
* request will come through here. A new thread will be spawned from the
* thread pool for each incoming message.
*
* @param message QuickFIX message
* @param sessionID QuickFIX session ID
* @throws FieldNotFound
* @throws IncorrectDataFormat
* @throws IncorrectTagValue
* @throws UnsupportedMessageType
*/
public void fromApp(Message message, SessionID sessionID) throws FieldNotFound, IncorrectDataFormat,
IncorrectTagValue, UnsupportedMessageType {
if (log.isDebugEnabled()) {
StringBuffer sb = new StringBuffer();
sb.append("Received FIX message from ").append(message.getHeader().getField(new SenderCompID()).getValue());
sb.append("\nMessage Sequence Number: ").append(message.getHeader().getField(new MsgSeqNum()).getValue());
sb.append("\nReceiver ID: ").append(message.getHeader().getField(new TargetCompID()).getValue());
log.debug(sb.toString());
if (log.isTraceEnabled()) {
log.trace("Message: " + message.toString());
}
}
AtomicInteger atomicCounter = countersMap.get(sessionID);
int counter = atomicCounter.incrementAndGet();
boolean rolled = atomicCounter.compareAndSet(FIXConstants.DEFAULT_COUNTER_UPPER_LIMIT, 0);
if (rolled && log.isDebugEnabled()) {
log.debug("Incoming request counter rolled over for the session: " + sessionID);
}
workerPool.execute(new FIXWorkerThread(message, sessionID, counter));
}
/**
* This Runnable class can be used when it is required to process each incoming message
* using separate threads.
*/
class FIXWorkerThread implements Runnable {
private Message message;
private SessionID sessionID;
private int counter;
public FIXWorkerThread(Message message, SessionID sessionID, int counter) {
this.message = message;
this.sessionID = sessionID;
this.counter = counter;
}
private void handleIncomingRequest() {
if (log.isDebugEnabled()) {
log.debug("Source session: " + sessionID + " - Received message with sequence " +
"number " + counter);
}
//Create message context for the incoming message
AbstractTransportListener trpListener = (AbstractTransportListener) cfgCtx.getAxisConfiguration().
getTransportIn(FIXConstants.TRANSPORT_NAME).getReceiver();
MessageContext msgCtx = trpListener.createMessageContext();
msgCtx.setProperty(Constants.OUT_TRANSPORT_INFO, new FIXOutTransportInfo(sessionID));
if (service != null) {
// Set the service for which the message is intended to
msgCtx.setAxisService(service);
// find the operation for the message, or default to one
Parameter operationParam = service.getParameter(BaseConstants.OPERATION_PARAM);
QName operationQName = (
operationParam != null ?
BaseUtils.getQNameFromString(operationParam.getValue()) :
BaseConstants.DEFAULT_OPERATION);
AxisOperation operation = service.getOperation(operationQName);
if (operation != null) {
msgCtx.setAxisOperation(operation);
msgCtx.setAxisMessage(operation.getMessage(WSDLConstants.MESSAGE_LABEL_IN_VALUE));
msgCtx.setSoapAction("urn:" + operation.getName().getLocalPart());
}
}
String fixApplication = FIXConstants.FIX_INITIATOR;
if (acceptor) {
fixApplication = FIXConstants.FIX_ACCEPTOR;
} else {
msgCtx.setProperty("synapse.isresponse", true);
}
try {
//Put the FIX message in a SOAPEnvelope
FIXUtils.getInstance().setSOAPEnvelope(message, counter, sessionID.toString(), msgCtx);
trpListener.handleIncomingMessage(
msgCtx,
FIXUtils.getTransportHeaders(service.getName(), fixApplication),
null,
FIXConstants.FIX_DEFAULT_CONTENT_TYPE
);
} catch (AxisFault e) {
handleException("Error while processing FIX message", e);
}
}
private void handleIncomingResponse(MessageContext outMsgCtx) {
AbstractTransportSender trpSender = (AbstractTransportSender) cfgCtx.getAxisConfiguration().
getTransportOut(FIXConstants.TRANSPORT_NAME).getSender();
MessageContext msgCtx = trpSender.createResponseMessageContext(outMsgCtx);
try {
//Put the FIX message in a SOAPEnvelope
FIXUtils.getInstance().setSOAPEnvelope(message, counter, sessionID.toString(), msgCtx);
msgCtx.setServerSide(true);
trpSender.handleIncomingMessage(
msgCtx,
FIXUtils.getTransportHeaders(service.getName(), FIXConstants.FIX_INITIATOR),
null,
FIXConstants.FIX_DEFAULT_CONTENT_TYPE
);
} catch (AxisFault e) {
handleException("Error while processing response FIX message", e);
}
}
public void run() {
if (allNewApproach) {
//treat all messages (including responses) as new messages
handleIncomingRequest();
}
else {
if (acceptor) {
//treat messages coming from an acceptor as new request messages
handleIncomingRequest();
}
else {
MessageContext outMsgCtx = outgoingMessages.poll();
if (outMsgCtx != null) {
//handle as a response to an outgoing message
handleIncomingResponse(outMsgCtx);
} else if (!dropExtraResponses) {
//handle as a new request message
handleIncomingRequest();
} else {
log.debug("Dropping additional FIX response");
}
}
}
}
}
}