/*
* 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.context.ConfigurationContext;
import org.apache.axis2.context.MessageContext;
import org.apache.axis2.description.AxisService;
import org.apache.axis2.description.Parameter;
import org.apache.axis2.description.TransportOutDescription;
import org.apache.axis2.transport.OutTransportInfo;
import org.apache.axis2.transport.base.AbstractTransportSender;
import org.apache.axis2.transport.base.BaseUtils;
import org.apache.axis2.transport.base.threads.WorkerPool;
import org.apache.axis2.transport.base.threads.WorkerPoolFactory;
import org.apache.commons.logging.LogFactory;
import quickfix.*;
import quickfix.field.*;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Hashtable;
import java.util.Map;
/**
* The FIX transport sender implementation. This implementation looks at the SOAPBody of the message
* context to identify how the message was first received by Axis2 engine and also looks at some FIX
* header fields to make the optimum routing decision.
* <p/>
* This transport sender implementation does not support forwarding FIX messages to sessions with
* different BeginString values.When it performs a message forwarding it makes sure the forwarding
* takes place according to the conditions specified in the 'Third Party Routing' section in the
* FIX protocol specification.
*/
public class FIXTransportSender extends AbstractTransportSender {
private FIXSessionFactory sessionFactory;
private FIXOutgoingMessageHandler messageSender;
private WorkerPool workerPool;
public FIXTransportSender() {
this.log = LogFactory.getLog(this.getClass());
}
/**
* @param cfgCtx the axis2 configuration context
* @param transportOut the Out Transport description
* @throws AxisFault on error
*/
public void init(ConfigurationContext cfgCtx, TransportOutDescription transportOut) throws AxisFault {
super.init(cfgCtx, transportOut);
this.sessionFactory = FIXSessionFactory.getInstance(new FIXApplicationFactory(cfgCtx));
this.workerPool = WorkerPoolFactory.getWorkerPool(
10, 20, 5, -1, "FIX Sender Worker thread group", "FIX-Worker");
this.sessionFactory.setSenderThreadPool(this.workerPool);
messageSender = new FIXOutgoingMessageHandler();
messageSender.setSessionFactory(this.sessionFactory);
log.info("FIX transport sender initialized...");
}
public void stop() {
try {
this.workerPool.shutdown(10000);
} catch (InterruptedException e) {
log.warn("Thread interrupted while waiting for worker pool to shut down");
}
sessionFactory.disposeFIXInitiators();
super.stop();
}
/**
* Performs the actual sending of the message.
*
* @param msgCtx the axis2 message context of the message to be sent
* @param targetEPR the EPR for which the message is to be sent
* @param outTransportInfo the OutTransportInfo for the message
* @throws AxisFault on error
*/
public void sendMessage(MessageContext msgCtx, String targetEPR,
OutTransportInfo outTransportInfo) throws AxisFault {
if (log.isDebugEnabled()) {
log.debug("Attempting to send a FIX message, Message ID:" + msgCtx.getMessageID());
}
Message fixMessage = null;
String serviceName = FIXUtils.getServiceName(msgCtx);
String fixApplication = FIXUtils.getFixApplication(msgCtx);
String sourceSession = FIXUtils.getSourceSession(msgCtx);
int counter = FIXUtils.getSequenceNumber(msgCtx);
try {
fixMessage = FIXUtils.getInstance().createFIXMessage(msgCtx);
} catch (IOException e) {
handleException("Exception occurred while creating the FIX message from SOAP Envelope", e);
}
if (FIXConstants.FIX_ACCEPTOR.equals(fixApplication)) {
//A message came in through an acceptor bound to a service
if (targetEPR != null) {
//Forward the message to the given EPR
sendUsingEPR(targetEPR, serviceName, fixMessage, sourceSession, counter, msgCtx);
} else if (outTransportInfo != null && outTransportInfo instanceof FIXOutTransportInfo) {
//Send the message back to the sender
sendUsingTrpOutInfo(outTransportInfo, serviceName, fixMessage,
sourceSession, counter, msgCtx);
}
} else if (FIXConstants.FIX_INITIATOR.equals(fixApplication)) {
if (sendUsingAcceptorSession(serviceName, fixMessage, sourceSession, counter, msgCtx)) {
return;
} else if (targetEPR != null) {
sendUsingEPR(targetEPR, serviceName, fixMessage, sourceSession, counter, msgCtx);
return;
}
handleException("Unable to find a session to send the message...");
} else {
//A message generated in Axis2 engine or a message arrived over a different transport
if (targetEPR != null) {
sendUsingEPR(targetEPR, serviceName, fixMessage, sourceSession, counter, msgCtx);
} else {
sendUsingAcceptorSession(serviceName, fixMessage, sourceSession, counter, msgCtx);
}
}
}
private boolean isTargetValid(Map<String, String> fieldValues, SessionID targetSession,
boolean beginStrValidation) {
String beginString = fieldValues.get(FIXConstants.BEGIN_STRING);
String deliverToCompID = fieldValues.get(FIXConstants.DELIVER_TO_COMP_ID);
String deliverToSubID = fieldValues.get(FIXConstants.DELIVER_TO_SUB_ID);
String deliverToLocationID = fieldValues.get(FIXConstants.DELIVER_TO_LOCATION_ID);
if (beginStrValidation && !targetSession.getBeginString().equals(beginString)) {
return false;
} else if (!targetSession.getTargetCompID().equals(deliverToCompID)) {
return false;
} else if (deliverToSubID != null && !deliverToSubID.equals(targetSession.getTargetSubID())) {
return false;
} else if (deliverToLocationID != null && !deliverToLocationID.equals(targetSession.getTargetLocationID())) {
return false;
}
return true;
}
/**
* Prepares the message to be forwarded according to the conditions specified in the FIX protocol
* specification.
*
* @param message the FIX message to be forwarded
* @param fieldValues a Map of field values for quick access
*/
private void prepareToForwardMessage(Message message, Map<String, String> fieldValues) {
//set OnBehalfOf* fields
message.getHeader().setField(new OnBehalfOfCompID(fieldValues.get(FIXConstants.SENDER_COMP_ID)));
if (fieldValues.get(FIXConstants.SENDER_SUB_ID) != null) {
message.getHeader().setField(new OnBehalfOfSubID(fieldValues.get(FIXConstants.SENDER_SUB_ID)));
}
if (fieldValues.get(FIXConstants.SENDER_LOCATION_ID) != null) {
message.getHeader().setField(new OnBehalfOfLocationID(fieldValues.get(FIXConstants.SENDER_LOCATION_ID)));
}
//remove additional Sender* fields and DeliverTo* fields
message.getHeader().removeField(SenderSubID.FIELD);
message.getHeader().removeField(SenderLocationID.FIELD);
message.getHeader().removeField(DeliverToCompID.FIELD);
message.getHeader().removeField(DeliverToSubID.FIELD);
message.getHeader().removeField(DeliverToLocationID.FIELD);
}
/**
* Puts DeliverToX fields in the message to enable the message to be forwarded at the destination.
*
* @param message the FIX message to be forwarded
* @param targetEPR the EPR to which the message will be sent
*/
private void setDeliverToXFields(Message message, String targetEPR) {
Hashtable<String, String> properties = BaseUtils.getEPRProperties(targetEPR);
String deliverTo = properties.get(FIXConstants.DELIVER_TO_COMP_ID);
//If a DeliverToCompID field is given in EPR put the field in the message
if (deliverTo != null) {
message.getHeader().setField(new DeliverToCompID(deliverTo));
deliverTo = properties.get(FIXConstants.DELIVER_TO_SUB_ID);
if (deliverTo != null) {
message.getHeader().setField(new DeliverToSubID(deliverTo));
}
deliverTo = properties.get(FIXConstants.DELIVER_TO_LOCATION_ID);
if (deliverTo != null) {
message.getHeader().setField(new DeliverToLocationID(deliverTo));
}
}
}
/**
* Puts DeliverToX fields in the message to enable the message to be forwarded at the destination.
*
* @param message the FIX message to be forwarded
* @param fieldValues the Map of field values for quick access
*/
private void setDeliverToXFields(Message message, Map<String, String> fieldValues) {
//Use the fields of the message to set DeliverToX fields
String onBehalf = fieldValues.get(FIXConstants.ON_BEHALF_OF_COMP_ID);
if (onBehalf != null) {
message.getHeader().setField(new DeliverToCompID(onBehalf));
onBehalf = fieldValues.get(FIXConstants.ON_BEHALF_OF_SUB_ID);
if (onBehalf != null) {
message.getHeader().setField(new DeliverToSubID(onBehalf));
}
onBehalf = fieldValues.get(FIXConstants.ON_BEHALF_OF_LOCATION_ID);
if (onBehalf != null) {
message.getHeader().setField(new DeliverToLocationID(onBehalf));
}
message.getHeader().removeField(OnBehalfOfCompID.FIELD);
message.getHeader().removeField(OnBehalfOfSubID.FIELD);
message.getHeader().removeField(OnBehalfOfLocationID.FIELD);
}
}
/**
* Puts DeliverToX fields in the message to enable the message to be forwarded at the destination.
* This method retrieves the parameters from the services.xml and put them in the message as
* DeliverToX fields. Should be used when a response message has to forwarded at the destination.
*
* @param message the FIX message to be forwarded
* @param service the AxisService of the message
*/
private void setDeliverToXFields(Message message, AxisService service) {
Parameter param = service.getParameter(FIXConstants.FIX_RESPONSE_DELIVER_TO_COMP_ID_PARAM);
if (param != null) {
message.getHeader().setField(new DeliverToCompID(param.getValue().toString()));
param = service.getParameter(FIXConstants.FIX_RESPONSE_DELIVER_TO_SUB_ID_PARAM);
if (param != null) {
message.getHeader().setField(new DeliverToSubID(param.getValue().toString()));
}
param = service.getParameter(FIXConstants.FIX_RESPONSE_DELIVER_TO_LOCATION_ID_PARAM);
if (param != null) {
message.getHeader().setField(new DeliverToLocationID(param.getValue().toString()));
}
}
}
/**
* Sends a FIX message to the given EPR
*
* @param targetEPR the EPR to which the message is sent to
* @param serviceName name of the service which processed the message
* @param fixMessage the FIX message
* @param srcSession String uniquely identifying the incoming session
* @param counter application level sequence number of the message
* @param msgCtx the Axis2 MessageContext for the message
* @return boolean value indicating the result
* @throws AxisFault on error
*/
private boolean sendUsingEPR(String targetEPR, String serviceName, Message fixMessage,
String srcSession, int counter, MessageContext msgCtx) throws AxisFault {
FIXOutTransportInfo fixOut = new FIXOutTransportInfo(targetEPR);
SessionID sessionID = fixOut.getSessionID();
Map<String, String> fieldValues = FIXUtils.getMessageForwardingParameters(fixMessage);
String beginString = fieldValues.get(FIXConstants.BEGIN_STRING);
String deliverToCompID = fieldValues.get(FIXConstants.DELIVER_TO_COMP_ID);
AxisService service = cfgCtx.getAxisConfiguration().getService(serviceName);
//match BeginString values
if (isValidationOn(service) && beginString != null && !beginString.equals(sessionID.getBeginString())) {
handleException("BeginString validation is on. Cannot forward messages to a session" +
" with a different BeginString");
}
if (deliverToCompID != null) {
//message needs to be delivered
if (!deliverToCompID.equals(sessionID.getTargetCompID())) {
handleException("Cannot forward messages that do not have a valid DeliverToCompID field");
} else {
prepareToForwardMessage(fixMessage, fieldValues);
setDeliverToXFields(fixMessage, targetEPR);
}
}
if (!Session.doesSessionExist(sessionID)) {
//try to create initiator to send the message
sessionFactory.createFIXInitiator(targetEPR, service, sessionID);
}
try {
messageSender.sendMessage(fixMessage, sessionID, srcSession, counter, msgCtx, targetEPR);
return true;
} catch (SessionNotFound e) {
log.error("Error while sending the FIX message. Session " + sessionID.toString() + " does" +
" not exist", e);
return false;
}
}
/**
* Sends a FIX message using the SessionID in the OutTransportInfo
*
* @param trpOutInfo the TransportOutInfo for the message
* @param fixMessage the FIX message to be sent
* @param srcSession String uniquely identifying the incoming session
* @param counter application level sequence number of the message
* @param serviceName name of the AxisService for the message
* @param msgCtx Axis2 MessageContext
* @return boolean value indicating the result
* @throws AxisFault on error
*/
private boolean sendUsingTrpOutInfo(OutTransportInfo trpOutInfo, String serviceName,
Message fixMessage, String srcSession, int counter,
MessageContext msgCtx) throws AxisFault {
FIXOutTransportInfo fixOut = (FIXOutTransportInfo) trpOutInfo;
SessionID sessionID = fixOut.getSessionID();
Map<String, String> fieldValues = FIXUtils.getMessageForwardingParameters(fixMessage);
String beginString = fieldValues.get(FIXConstants.BEGIN_STRING);
String deliverToCompID = fieldValues.get(FIXConstants.DELIVER_TO_COMP_ID);
AxisService service = cfgCtx.getAxisConfiguration().getService(serviceName);
//match BeginString values
if (isValidationOn(service) && beginString != null && !beginString.equals(sessionID.getBeginString())) {
handleException("BeginString validation is on. Cannot forward messages to a session" +
" with a different BeginString");
}
if (deliverToCompID != null) {
//message needs to be delivered to some other party
if (!deliverToCompID.equals(sessionID.getTargetCompID())) {
handleException("Cannot forward messages that do not have a valid DeliverToCompID field");
} else {
prepareToForwardMessage(fixMessage, fieldValues);
setDeliverToXFields(fixMessage, service);
}
} else {
setDeliverToXFields(fixMessage, fieldValues);
}
try {
messageSender.sendMessage(fixMessage, sessionID, srcSession, counter, msgCtx, null);
return true;
} catch (SessionNotFound e) {
log.error("Error while sending the FIX message. Session " + sessionID.toString() + " does" +
" not exist", e);
return false;
}
}
/**
* Send the message using a session in the acceptor side
*
* @param serviceName the service of the message
* @param fixMessage the FIX message to be sent
* @param srcSession String uniquely identifying the incoming session
* @param counter the application level sequence number of the message
* @param msgCtx Axi2 MessageContext
* @return boolean value indicating the result
* @throws AxisFault on error
*/
private boolean sendUsingAcceptorSession(String serviceName, Message fixMessage, String srcSession,
int counter, MessageContext msgCtx) throws AxisFault {
Map<String, String> fieldValues = FIXUtils.getMessageForwardingParameters(fixMessage);
String deliverToCompID = fieldValues.get(FIXConstants.DELIVER_TO_COMP_ID);
Acceptor acceptor = sessionFactory.getAcceptor(serviceName);
SessionID sessionID = null;
AxisService service = cfgCtx.getAxisConfiguration().getService(serviceName);
if (acceptor != null) {
ArrayList<SessionID> sessions = acceptor.getSessions();
if (sessions.size() == 1) {
sessionID = sessions.get(0);
if (deliverToCompID != null && !isTargetValid(fieldValues, sessionID, isValidationOn(service))) {
sessionID = null;
}
} else if (sessions.size() > 1 && deliverToCompID != null) {
for (SessionID session : sessions) {
sessionID = session;
if (isTargetValid(fieldValues, sessionID, isValidationOn(service))) {
break;
}
}
}
}
if (sessionID != null) {
//Found a valid session. Now forward the message...
FIXOutTransportInfo fixOutInfo = new FIXOutTransportInfo(sessionID);
return sendUsingTrpOutInfo(fixOutInfo, serviceName, fixMessage,
srcSession, counter, msgCtx);
}
return false;
}
/**
* Checks whether BeginString validation is on for the specified
* service.
*
* @param service the AxisService of the message
* @return a boolean value indicating the validation state
*/
private boolean isValidationOn(AxisService service) {
Parameter validationParam = service.getParameter(FIXConstants.FIX_BEGIN_STRING_VALIDATION);
if (validationParam != null) {
if ("true".equals(validationParam.getValue().toString())) {
return true;
}
}
return false;
}
public void logOutIncomingSession(SessionID sessionID) {
messageSender.cleanUpMessages(sessionID.toString());
}
}