/*******************************************************************************
* Copyright (c) quickfixengine.org All rights reserved.
*
* This file is part of the QuickFIX FIX Engine
*
* This file may be distributed under the terms of the quickfixengine.org
* license as defined by quickfixengine.org and appearing in the file
* LICENSE included in the packaging of this file.
*
* This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING
* THE WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A
* PARTICULAR PURPOSE.
*
* See http://www.quickfixengine.org/LICENSE for licensing information.
*
* Contact ask@quickfixengine.org if any conditions of this licensing
* are not clear to you.
******************************************************************************/
package quickfix;
import org.w3c.dom.CDATASection;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import quickfix.field.ApplVerID;
import quickfix.field.BeginString;
import quickfix.field.BodyLength;
import quickfix.field.CheckSum;
import quickfix.field.CstmApplVerID;
import quickfix.field.DeliverToCompID;
import quickfix.field.DeliverToLocationID;
import quickfix.field.DeliverToSubID;
import quickfix.field.LastMsgSeqNumProcessed;
import quickfix.field.MessageEncoding;
import quickfix.field.MsgSeqNum;
import quickfix.field.MsgType;
import quickfix.field.NoHops;
import quickfix.field.OnBehalfOfCompID;
import quickfix.field.OnBehalfOfLocationID;
import quickfix.field.OnBehalfOfSendingTime;
import quickfix.field.OnBehalfOfSubID;
import quickfix.field.OrigSendingTime;
import quickfix.field.PossDupFlag;
import quickfix.field.PossResend;
import quickfix.field.SecureDataLen;
import quickfix.field.SenderCompID;
import quickfix.field.SenderLocationID;
import quickfix.field.SenderSubID;
import quickfix.field.SendingTime;
import quickfix.field.SessionRejectReason;
import quickfix.field.Signature;
import quickfix.field.SignatureLength;
import quickfix.field.TargetCompID;
import quickfix.field.TargetLocationID;
import quickfix.field.TargetSubID;
import quickfix.field.XmlData;
import quickfix.field.XmlDataLen;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import java.io.ByteArrayOutputStream;
import java.text.DecimalFormat;
import java.util.Iterator;
import java.util.List;
/**
* Represents a FIX message.
*/
public class Message extends FieldMap {
static final long serialVersionUID = -3193357271891865972L;
protected Header header = new Header();
protected Trailer trailer = new Trailer();
// @GuardedBy("this")
private FieldException exception;
public Message() {
// empty
}
protected Message(int[] fieldOrder) {
super(fieldOrder);
}
public Message(String string) throws InvalidMessage {
fromString(string, null, true);
}
public Message(String string, boolean validate) throws InvalidMessage {
fromString(string, null, validate);
}
public Message(String string, DataDictionary dd) throws InvalidMessage {
fromString(string, dd, true);
}
public Message(String string, DataDictionary dd, boolean validate) throws InvalidMessage {
fromString(string, dd, validate);
}
public static boolean InitializeXML(String url) {
throw new UnsupportedOperationException();
}
@Override
public Object clone() {
try {
final Message message = getClass().newInstance();
return cloneTo(message);
} catch (final InstantiationException e) {
throw new RuntimeException(e);
} catch (final IllegalAccessException e) {
throw new RuntimeException(e);
}
}
private Object cloneTo(Message message) {
message.initializeFrom(this);
message.header.initializeFrom(getHeader());
message.trailer.initializeFrom(getTrailer());
return message;
}
@Override
public String toString() {
header.setField(new BodyLength(bodyLength()));
trailer.setField(new CheckSum(checkSum()));
final StringBuffer sb = new StringBuffer();
header.calculateString(sb, null, null);
calculateString(sb, null, null);
trailer.calculateString(sb, null, null);
return sb.toString();
}
public int bodyLength() {
return header.calculateLength() + calculateLength() + trailer.calculateLength();
}
private static DecimalFormat checksumFormat = new DecimalFormat("000");
private int checkSum(String s) {
final int offset = s.lastIndexOf("\00110=");
int sum = 0;
for (int i = 0; i < offset; i++) {
sum += s.charAt(i);
}
return (sum + 1) % 256;
}
private String checkSum() {
return checksumFormat.format((header.calculateTotal() + calculateTotal() + trailer
.calculateTotal()) % 256);
}
public void headerAddGroup(Group group) {
header.addGroup(group);
}
public void headerReplaceGroup(int num, Group group) {
header.replaceGroup(num, group);
}
public Group headerGetGroup(int num, Group group) throws FieldNotFound {
return header.getGroup(num, group);
}
public void headerRemoveGroup(Group group) {
header.removeGroup(group);
}
public boolean headerHasGroup(int field) {
return header.hasGroup(field);
}
public boolean headerHasGroup(int num, int field) {
return header.hasGroup(num, field);
}
public boolean headerHasGroup(int num, Group group) {
return headerHasGroup(num, group.getFieldTag());
}
public boolean headerHasGroup(Group group) {
return headerHasGroup(group.getFieldTag());
}
public void trailerAddGroup(Group group) {
trailer.addGroup(group);
}
public Group trailerGetGroup(int num, Group group) throws FieldNotFound {
return trailer.getGroup(num, group);
}
public void trailerReplaceGroup(int num, Group group) {
trailer.replaceGroup(num, group);
}
public void trailerRemoveGroup(Group group) {
trailer.removeGroup(group);
}
public boolean trailerHasGroup(int field) {
return trailer.hasGroup(field);
}
public boolean trailerHasGroup(int num, int field) {
return trailer.hasGroup(num, field);
}
public boolean trailerHasGroup(int num, Group group) {
return trailerHasGroup(num, group.getFieldTag());
}
public boolean trailerHasGroup(Group group) {
return trailerHasGroup(group.getFieldTag());
}
/**
* Converts the message into a simple XML format. This format is
* probably not sufficient for production use, but it more intended
* for diagnostics and debugging. THIS IS NOT FIXML.
*
* To get names instead of tag number, use toXML(DataDictionary)
* instead.
*
* @return an XML representation of the message.
* @see #toXML(DataDictionary)
*/
public String toXML() {
return toXML(null);
}
/**
* Converts the message into a simple XML format. This format is
* probably not sufficient for production use, but it more intended
* for diagnostics and debugging. THIS IS NOT FIXML.
*
* @param dataDictionary
* @return the XML representation of the message
*/
public String toXML(DataDictionary dataDictionary) {
try {
final Document document = DocumentBuilderFactory.newInstance().newDocumentBuilder()
.newDocument();
final Element message = document.createElement("message");
document.appendChild(message);
toXMLFields(message, "header", header, dataDictionary);
toXMLFields(message, "body", this, dataDictionary);
toXMLFields(message, "trailer", trailer, dataDictionary);
final DOMSource domSource = new DOMSource(document);
final ByteArrayOutputStream out = new ByteArrayOutputStream();
final StreamResult streamResult = new StreamResult(out);
final TransformerFactory tf = TransformerFactory.newInstance();
final Transformer serializer = tf.newTransformer();
serializer.setOutputProperty(OutputKeys.ENCODING, "ISO-8859-1");
serializer.setOutputProperty(OutputKeys.INDENT, "yes");
serializer.transform(domSource, streamResult);
return out.toString();
} catch (final Exception e) {
throw new RuntimeException(e);
}
}
private void toXMLFields(Element message, String section, FieldMap fieldMap,
DataDictionary dataDictionary) throws FieldNotFound {
final Document document = message.getOwnerDocument();
final Element fields = document.createElement(section);
message.appendChild(fields);
final Iterator<Field<?>> fieldItr = fieldMap.iterator();
while (fieldItr.hasNext()) {
final Field<?> field = fieldItr.next();
final Element fieldElement = document.createElement("field");
if (dataDictionary != null) {
final String name = dataDictionary.getFieldName(field.getTag());
if (name != null) {
fieldElement.setAttribute("name", name);
}
final String enumValue = dataDictionary.getValueName(field.getTag(), field
.getObject().toString());
if (enumValue != null) {
fieldElement.setAttribute("enum", enumValue);
}
}
fieldElement.setAttribute("tag", Integer.toString(field.getTag()));
final CDATASection value = document.createCDATASection(field.getObject().toString());
fieldElement.appendChild(value);
fields.appendChild(fieldElement);
}
final Iterator<Integer> groupKeyItr = fieldMap.groupKeyIterator();
while (groupKeyItr.hasNext()) {
final int groupKey = (groupKeyItr.next()).intValue();
final Element groupsElement = document.createElement("groups");
fields.appendChild(groupsElement);
if (dataDictionary != null) {
final String name = dataDictionary.getFieldName(groupKey);
if (name != null) {
groupsElement.setAttribute("name", name);
}
}
groupsElement.setAttribute("tag", Integer.toString(groupKey));
final List<Group> groups = fieldMap.getGroups(groupKey);
final Iterator<Group> groupItr = groups.iterator();
while (groupItr.hasNext()) {
final Group group = groupItr.next();
toXMLFields(groupsElement, "group", group, dataDictionary);
}
}
}
public final Header getHeader() {
return header;
}
public final Trailer getTrailer() {
return trailer;
}
public boolean isAdmin() {
if (header.isSetField(MsgType.FIELD)) {
try {
final String msgType = header.getString(MsgType.FIELD);
return MessageUtils.isAdminMessage(msgType);
} catch (final FieldNotFound e) {
// shouldn't happen
}
}
return false;
}
public boolean isApp() {
return !isAdmin();
}
@Override
public boolean isEmpty() {
return super.isEmpty() && header.isEmpty() && trailer.isEmpty() && position == 0;
}
@Override
public void clear() {
super.clear();
header.clear();
trailer.clear();
position = 0;
}
public static class Header extends FieldMap {
static final long serialVersionUID = -3193357271891865972L;
private static final int[] EXCLUDED_HEADER_FIELDS = { BeginString.FIELD, BodyLength.FIELD,
MsgType.FIELD };
public Header() {
super();
}
public Header(int[] fieldOrder) {
super(fieldOrder);
}
@Override
protected void calculateString(StringBuffer buffer, int[] excludedFields, int[] postFields) {
super.calculateString(buffer, EXCLUDED_HEADER_FIELDS, postFields);
}
}
public static class Trailer extends FieldMap {
static final long serialVersionUID = -3193357271891865972L;
private static final int[] TRAILER_FIELD_ORDER = { SignatureLength.FIELD, Signature.FIELD,
CheckSum.FIELD };
public Trailer() {
super(TRAILER_FIELD_ORDER);
}
public Trailer(int[] fieldOrder) {
super(fieldOrder);
}
@Override
protected void calculateString(StringBuffer buffer, int[] excludedFields, int[] postFields) {
super.calculateString(buffer, null, new int[] { CheckSum.FIELD });
}
}
public void reverseRoute(Header header) throws FieldNotFound {
this.header.removeField(BeginString.FIELD);
this.header.removeField(SenderCompID.FIELD);
this.header.removeField(SenderSubID.FIELD);
this.header.removeField(SenderLocationID.FIELD);
this.header.removeField(TargetCompID.FIELD);
this.header.removeField(TargetSubID.FIELD);
this.header.removeField(TargetLocationID.FIELD);
if (header.isSetField(BeginString.FIELD)) {
copyField(header, BeginString.FIELD, BeginString.FIELD);
copyField(header, SenderCompID.FIELD, TargetCompID.FIELD);
copyField(header, SenderSubID.FIELD, TargetSubID.FIELD);
copyField(header, SenderLocationID.FIELD, TargetLocationID.FIELD);
copyField(header, TargetCompID.FIELD, SenderCompID.FIELD);
copyField(header, TargetSubID.FIELD, SenderSubID.FIELD);
copyField(header, TargetLocationID.FIELD, SenderLocationID.FIELD);
this.header.removeField(OnBehalfOfCompID.FIELD);
this.header.removeField(OnBehalfOfSubID.FIELD);
this.header.removeField(DeliverToCompID.FIELD);
this.header.removeField(DeliverToSubID.FIELD);
copyField(header, OnBehalfOfCompID.FIELD, DeliverToCompID.FIELD);
copyField(header, OnBehalfOfSubID.FIELD, DeliverToSubID.FIELD);
copyField(header, DeliverToCompID.FIELD, OnBehalfOfCompID.FIELD);
copyField(header, DeliverToSubID.FIELD, OnBehalfOfSubID.FIELD);
this.header.removeField(OnBehalfOfLocationID.FIELD);
this.header.removeField(DeliverToLocationID.FIELD);
if (header.getString(BeginString.FIELD).compareTo(FixVersions.BEGINSTRING_FIX41) >= 0) {
copyField(header, OnBehalfOfLocationID.FIELD, DeliverToLocationID.FIELD);
copyField(header, DeliverToLocationID.FIELD, OnBehalfOfLocationID.FIELD);
}
}
}
private void copyField(Header header, int fromField, int toField) throws FieldNotFound {
if (header.isSetField(fromField)) {
final String value = header.getString(fromField);
if (value.length() > 0) {
this.header.setString(toField, value);
}
}
}
public void setSessionID(SessionID sessionID) {
header.setString(BeginString.FIELD, sessionID.getBeginString());
header.setString(SenderCompID.FIELD, sessionID.getSenderCompID());
optionallySetID(header, SenderSubID.FIELD, sessionID.getSenderSubID());
optionallySetID(header, SenderLocationID.FIELD, sessionID.getSenderLocationID());
header.setString(TargetCompID.FIELD, sessionID.getTargetCompID());
optionallySetID(header, TargetSubID.FIELD, sessionID.getTargetSubID());
optionallySetID(header, TargetLocationID.FIELD, sessionID.getTargetLocationID());
}
private void optionallySetID(Header header, int field, String value) {
if (!value.equals(SessionID.NOT_SET)) {
header.setString(field, value);
}
}
public void fromString(String messageData, DataDictionary dd, boolean doValidation)
throws InvalidMessage {
parse(messageData, dd, dd, doValidation);
}
public void fromString(String messageData, DataDictionary sessionDictionary,
DataDictionary applicationDictionary, boolean doValidation) throws InvalidMessage {
if (sessionDictionary.isAdminMessage(MessageUtils.getMessageType(messageData))) {
applicationDictionary = sessionDictionary;
}
parse(messageData, sessionDictionary, applicationDictionary, doValidation);
}
void parse(String messageData, DataDictionary sessionDataDictionary,
DataDictionary applicationDataDictionary, boolean doValidation) throws InvalidMessage {
this.messageData = messageData;
try {
parseHeader(sessionDataDictionary, doValidation);
parseBody(applicationDataDictionary, doValidation);
parseTrailer(sessionDataDictionary);
if (doValidation) {
validateCheckSum(messageData);
}
} catch (final FieldException e) {
exception = e;
}
}
private void validateCheckSum(String messageData) throws InvalidMessage {
try {
// Body length is checked at the protocol layer
final int checkSum = trailer.getInt(CheckSum.FIELD);
if (checkSum != checkSum(messageData)) {
// message will be ignored if checksum is wrong or missing
throw new InvalidMessage("Expected CheckSum=" + checkSum(messageData) + ", Received CheckSum="
+ checkSum + " in " + messageData);
}
} catch (final FieldNotFound e) {
throw new InvalidMessage("Field not found: " + e.field + " in " + messageData);
}
}
private void parseHeader(DataDictionary dd, boolean doValidation) throws InvalidMessage {
final boolean validHeaderFieldOrder = isNextField(dd, header, BeginString.FIELD)
&& isNextField(dd, header, BodyLength.FIELD)
&& isNextField(dd, header, MsgType.FIELD);
if (doValidation && !validHeaderFieldOrder) {
// Invalid message preamble (first three fields) is a serious
// condition and is handled differently from other message parsing errors.
throw new InvalidMessage("Header fields out of order in " + messageData);
}
StringField field = extractField(dd, header);
while (field != null && isHeaderField(field, dd)) {
header.setField(field);
if (dd != null && dd.isGroup(DataDictionary.HEADER_ID, field.getField())) {
parseGroup(DataDictionary.HEADER_ID, field, dd, header);
}
field = extractField(dd, header);
}
pushBack(field);
}
private boolean isNextField(DataDictionary dd, Header fields, int tag) throws InvalidMessage {
final StringField field = extractField(dd, header);
if (field == null || field.getTag() != tag) {
return false;
}
fields.setField(field);
return true;
}
private String getMsgType() throws InvalidMessage {
String res = null;
try {
res = header.getString(MsgType.FIELD);
} catch (final FieldNotFound e) {
throw new InvalidMessage(e.getMessage() + " in " + messageData);
}
return res;
}
private void parseBody(DataDictionary dd, boolean doValidation) throws InvalidMessage {
StringField field = extractField(dd, this);
while (field != null) {
if (isTrailerField(field.getField())) {
pushBack(field);
return;
}
if (isHeaderField(field.getField())) {
// An acceptance examples requires the sequence number to
// be available even if the related field is out of order
setField(header, field);
// Group case
if (dd != null && dd.isGroup(DataDictionary.HEADER_ID, field.getField())) {
parseGroup(DataDictionary.HEADER_ID, field, dd, header);
}
if (doValidation && dd != null && dd.isCheckFieldsOutOfOrder()) throw new FieldException(SessionRejectReason.TAG_SPECIFIED_OUT_OF_REQUIRED_ORDER,
field.getTag());
} else {
setField(this, field);
// Group case
if (dd != null && dd.isGroup(getMsgType(), field.getField())) {
parseGroup(getMsgType(), field, dd, this);
}
}
field = extractField(dd, this);
}
}
private void setField(FieldMap fields, StringField field) {
if (fields.isSetField(field)) {
throw new FieldException(SessionRejectReason.TAG_APPEARS_MORE_THAN_ONCE, field.getTag());
}
fields.setField(field);
}
private void parseGroup(String msgType, StringField field, DataDictionary dd, FieldMap parent)
throws InvalidMessage {
final DataDictionary.GroupInfo rg = dd.getGroup(msgType, field.getField());
final DataDictionary groupDataDictionary = rg.getDataDictionary();
final int[] fieldOrder = groupDataDictionary.getOrderedFields();
int previousOffset = -1;
final int groupCountTag = field.getField();
final int declaredGroupCount = Integer.parseInt(field.getValue());
parent.setField(groupCountTag, field);
final int firstField = rg.getDelimiterField();
boolean firstFieldFound = false;
Group group = null;
boolean inGroupParse = true;
while (inGroupParse) {
field = extractField(group, dd, parent);
if (field.getTag() == firstField) {
if (group != null) {
parent.addGroupRef(group);
}
group = new Group(groupCountTag, firstField, groupDataDictionary.getOrderedFields());
group.setField(field);
firstFieldFound = true;
previousOffset = -1;
} else {
if (groupDataDictionary.isGroup(msgType, field.getField())) {
if (firstFieldFound) {
parseGroup(msgType, field, groupDataDictionary, group);
} else {
throw new InvalidMessage("The group " + groupCountTag
+ " must set the delimiter field " + firstField + " in " + messageData);
}
} else {
if (groupDataDictionary.isField(field.getTag())) {
if (!firstFieldFound) {
throw new FieldException(
SessionRejectReason.REPEATING_GROUP_FIELDS_OUT_OF_ORDER, field
.getTag());
}
if (fieldOrder != null && dd.isCheckUnorderedGroupFields()) {
final int offset = index(fieldOrder, field.getTag());
if (offset >= 0) {
if (offset > previousOffset) {
previousOffset = offset;
} else {
throw new FieldException(
SessionRejectReason.REPEATING_GROUP_FIELDS_OUT_OF_ORDER,
field.getTag());
}
}
}
if (group != null) {
group.setField(field);
}
} else {
if (group != null) {
parent.addGroupRef(group);
}
pushBack(field);
inGroupParse = false;
}
}
}
}
// For later validation that the group size matches the parsed group count
parent.setGroupCount(groupCountTag, declaredGroupCount);
}
private int index(int[] fieldOrder, int tag) {
for (int i = 0; i < fieldOrder.length; i++) {
if (fieldOrder[i] == tag) {
return i;
}
}
return -1;
}
private void parseTrailer(DataDictionary dd) throws InvalidMessage {
StringField field = extractField(dd, trailer);
while (field != null) {
if (!isTrailerField(field, dd)) {
throw new FieldException(SessionRejectReason.TAG_SPECIFIED_OUT_OF_REQUIRED_ORDER,
field.getTag());
}
trailer.setField(field);
field = extractField(dd, trailer);
}
}
static boolean isHeaderField(Field<?> field, DataDictionary dd) {
return isHeaderField(field.getField())
|| (dd != null && dd.isHeaderField(field.getField()));
}
static boolean isHeaderField(int field) {
switch (field) {
case BeginString.FIELD:
case BodyLength.FIELD:
case MsgType.FIELD:
case SenderCompID.FIELD:
case TargetCompID.FIELD:
case OnBehalfOfCompID.FIELD:
case DeliverToCompID.FIELD:
case SecureDataLen.FIELD:
case MsgSeqNum.FIELD:
case SenderSubID.FIELD:
case SenderLocationID.FIELD:
case TargetSubID.FIELD:
case TargetLocationID.FIELD:
case OnBehalfOfSubID.FIELD:
case OnBehalfOfLocationID.FIELD:
case DeliverToSubID.FIELD:
case DeliverToLocationID.FIELD:
case PossDupFlag.FIELD:
case PossResend.FIELD:
case SendingTime.FIELD:
case OrigSendingTime.FIELD:
case XmlDataLen.FIELD:
case XmlData.FIELD:
case MessageEncoding.FIELD:
case LastMsgSeqNumProcessed.FIELD:
case OnBehalfOfSendingTime.FIELD:
case ApplVerID.FIELD:
case CstmApplVerID.FIELD:
case NoHops.FIELD:
return true;
default:
return false;
}
}
static boolean isTrailerField(Field<?> field, DataDictionary dd) {
return isTrailerField(field.getField())
|| (dd != null && dd.isTrailerField(field.getField()));
}
static boolean isTrailerField(int field) {
switch (field) {
case SignatureLength.FIELD:
case Signature.FIELD:
case CheckSum.FIELD:
return true;
default:
return false;
}
}
//
// Extract field
//
private String messageData;
private int position;
private StringField pushedBackField;
public void pushBack(StringField field) {
pushedBackField = field;
}
private StringField extractField(DataDictionary dataDictionary, FieldMap fields)
throws InvalidMessage {
return extractField(null, dataDictionary, fields);
}
private StringField extractField(Group group, DataDictionary dataDictionary, FieldMap fields)
throws InvalidMessage {
if (pushedBackField != null) {
final StringField f = pushedBackField;
pushedBackField = null;
return f;
}
if (position >= messageData.length()) {
return null;
}
final int equalsOffset = messageData.indexOf('=', position);
if (equalsOffset == -1) {
throw new InvalidMessage("Equal sign not found in field" + " in " + messageData);
}
int tag = -1;
try {
tag = Integer.parseInt(messageData.substring(position, equalsOffset));
} catch (final NumberFormatException e) {
position = messageData.indexOf('\001', position + 1) + 1;
throw new InvalidMessage("Bad tag format: " + e.getMessage() + " in " + messageData);
}
int sohOffset = messageData.indexOf('\001', equalsOffset + 1);
if (sohOffset == -1) {
throw new InvalidMessage("SOH not found at end of field: " + tag + " in " + messageData);
}
if (dataDictionary != null && dataDictionary.isDataField(tag)) {
/* Assume length field is 1 less. */
int lengthField = tag - 1;
/* Special case for Signature which violates above assumption. */
if (tag == 89) {
lengthField = 93;
}
int fieldLength;
try {
if (group == null) {
fieldLength = fields.getInt(lengthField);
} else {
fieldLength = group.getInt(lengthField);
}
} catch (final FieldNotFound e1) {
throw new InvalidMessage("Tag " + e1.field + " not found in " + messageData);
}
sohOffset = equalsOffset + 1 + fieldLength;
}
position = sohOffset + 1;
return new StringField(tag, messageData.substring(equalsOffset + 1, sohOffset));
}
/**
* Queries message structural validity.
*
* @return flag indicating whether the message has a valid structure
*/
synchronized boolean hasValidStructure() {
return exception == null;
}
public synchronized FieldException getException() {
return exception;
}
/**
* Returns the first invalid tag, which is all that can be reported
* in the resulting FIX reject message.
*
* @return the first invalid tag
*/
synchronized int getInvalidTag() {
return exception != null ? exception.getField() : 0;
}
/**
* Returns the msg type specified in a FIX message string.
* @param message the FIX message string
* @return the message type
* @throws MessageParseError (QF JNI compatibility)
*/
public static MsgType identifyType(String message) throws MessageParseError {
try {
return new MsgType(MessageUtils.getMessageType(message));
} catch (final InvalidMessage e) {
throw new MessageParseError(e.getMessage(), e);
}
}
}