Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
60 changes: 33 additions & 27 deletions quickfixj-core/src/main/java/quickfix/Message.java
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,16 @@

package quickfix;

import java.io.ByteArrayOutputStream;
import java.text.DecimalFormat;
import java.util.Iterator;
import java.util.List;
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 org.quickfixj.CharsetSupport;
import org.w3c.dom.CDATASection;
import org.w3c.dom.Document;
Expand Down Expand Up @@ -57,17 +67,6 @@
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.
*/
Expand Down Expand Up @@ -509,7 +508,7 @@ && isNextField(dd, header, BodyLength.FIELD)
header.setField(field);

if (dd != null && dd.isGroup(DataDictionary.HEADER_ID, field.getField())) {
parseGroup(DataDictionary.HEADER_ID, field, dd, header, doValidation);
parseGroup(DataDictionary.HEADER_ID, field, dd, dd, header, doValidation);
}

field = extractField(dd, header);
Expand Down Expand Up @@ -548,7 +547,7 @@ private void parseBody(DataDictionary dd, boolean doValidation) throws InvalidMe
setField(header, field);
// Group case
if (dd != null && dd.isGroup(DataDictionary.HEADER_ID, field.getField())) {
parseGroup(DataDictionary.HEADER_ID, field, dd, header, doValidation);
parseGroup(DataDictionary.HEADER_ID, field, dd, dd, header, doValidation);
}
if (doValidation && dd != null && dd.isCheckFieldsOutOfOrder())
throw new FieldException(SessionRejectReason.TAG_SPECIFIED_OUT_OF_REQUIRED_ORDER,
Expand All @@ -557,7 +556,7 @@ private void parseBody(DataDictionary dd, boolean doValidation) throws InvalidMe
setField(this, field);
// Group case
if (dd != null && dd.isGroup(getMsgType(), field.getField())) {
parseGroup(getMsgType(), field, dd, this, doValidation);
parseGroup(getMsgType(), field, dd, dd, this, doValidation);
}
}

Expand All @@ -572,7 +571,7 @@ private void setField(FieldMap fields, StringField field) {
fields.setField(field);
}

private void parseGroup(String msgType, StringField field, DataDictionary dd, FieldMap parent, boolean doValidation)
private void parseGroup(String msgType, StringField field, DataDictionary dd, DataDictionary parentDD, FieldMap parent, boolean doValidation)
throws InvalidMessage {
final DataDictionary.GroupInfo rg = dd.getGroup(msgType, field.getField());
final DataDictionary groupDataDictionary = rg.getDataDictionary();
Expand Down Expand Up @@ -602,14 +601,14 @@ private void parseGroup(String msgType, StringField field, DataDictionary dd, Fi
previousOffset = -1;
// QFJ-742
if (groupDataDictionary.isGroup(msgType, tag)) {
parseGroup(msgType, field, groupDataDictionary, group, doValidation);
parseGroup(msgType, field, groupDataDictionary, parentDD, group, doValidation);
}
} else if (groupDataDictionary.isGroup(msgType, tag)) {
if (!firstFieldFound) {
throw new InvalidMessage("The group " + groupCountTag
+ " must set the delimiter field " + firstField + " in " + messageData);
}
parseGroup(msgType, field, groupDataDictionary, group, doValidation);
parseGroup(msgType, field, groupDataDictionary, parentDD, group, doValidation);
} else if (groupDataDictionary.isField(tag)) {
if (!firstFieldFound) {
throw new FieldException(
Expand All @@ -629,16 +628,8 @@ private void parseGroup(String msgType, StringField field, DataDictionary dd, Fi
group.setField(field);
} else {
// QFJ-169/QFJ-791: handle unknown repeating group fields in the body
if (!(DataDictionary.HEADER_ID.equals(msgType))) {
if (!isTrailerField(tag) && !dd.isMsgField(msgType, tag)) {
if (doValidation) {
boolean fail = dd.checkFieldFailure(tag, false);
if (fail) {
throw new FieldException(
SessionRejectReason.TAG_NOT_DEFINED_FOR_THIS_MESSAGE_TYPE, tag);
}
}
group.setField(field);
if (!isTrailerField(tag) && !(DataDictionary.HEADER_ID.equals(msgType))) {
if (checkFieldValidation(parent, parentDD, field, msgType, doValidation, group)) {
continue;
}
}
Expand All @@ -654,6 +645,21 @@ private void parseGroup(String msgType, StringField field, DataDictionary dd, Fi
parent.setGroupCount(groupCountTag, declaredGroupCount);
}

private boolean checkFieldValidation(FieldMap parent, DataDictionary parentDD, StringField field, String msgType, boolean doValidation, Group group) throws FieldException {
boolean isField = (parent instanceof Group) ? parentDD.isField(field.getTag()) : parentDD.isMsgField(msgType, field.getTag());
if (!isField) {
if (doValidation) {
boolean fail = parentDD.checkFieldFailure(field.getTag(), false);
if (fail) {
throw new FieldException(SessionRejectReason.TAG_NOT_DEFINED_FOR_THIS_MESSAGE_TYPE, field.getTag());
}
}
group.setField(field);
return true;
}
return false;
}

private void parseTrailer(DataDictionary dd) throws InvalidMessage {
StringField field = extractField(dd, trailer);
while (field != null) {
Expand Down
58 changes: 58 additions & 0 deletions quickfixj-core/src/test/java/quickfix/MessageTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -1602,6 +1602,64 @@ public void testInvalidFieldInGroup() throws Exception {
assertFalse(group.isSetField(QuoteAckStatus.FIELD));
}

@Test
// QFJ-169/QFJ-791
public void testNestedRepeatingGroup()
throws Exception {

String newOrdersSingleString = "8=FIX.4.4|9=265|35=D|34=62|49=sender|52=20160803-12:55:42.094|"
+ "56=target|11=16H03A0000021|15=CHF|22=4|38=13|40=2|44=132|48=CH000000000|54=1|55=[N/A]|59=0|"
+ "60=20160803-12:55:41.866|207=XXXX|423=2|526=foo|528=P|"
// tag 20000 is not defined, tag 22000 is defined for NewOrderSingle in FIX44_Custom_Test.xml
+ "453=1|448=test|447=D|452=7|20000=0|802=1|523=test|803=25|22000=foobar|10=244|";

quickfix.fix44.NewOrderSingle nos = new quickfix.fix44.NewOrderSingle();
// using custom dictionary with user-defined tag 22000
final DataDictionary dataDictionary = new DataDictionary("FIX44_Custom_Test.xml");
dataDictionary.setCheckUserDefinedFields(false);
nos.fromString(newOrdersSingleString.replaceAll("\\|", "\001"), dataDictionary, true);
assertNull(nos.getException());
dataDictionary.validate(nos);

// defined tag should be set on the message
assertTrue(nos.isSetField(22000));
// undefined tag should not be set on the message
assertFalse(nos.isSetField(20000));
Group partyGroup = nos.getGroup(1, quickfix.field.NoPartyIDs.FIELD);
// undefined tag should be set on the group instead
assertTrue(partyGroup.isSetField(20000));
assertFalse(partyGroup.getGroup(1, quickfix.field.NoPartySubIDs.FIELD).isSetField(20000));
}

@Test
// QFJ-169/QFJ-791
public void testNestedRepeatingSubGroup()
throws Exception {

String newOrdersSingleString = "8=FIX.4.4|9=265|35=D|34=62|49=sender|52=20160803-12:55:42.094|"
+ "56=target|11=16H03A0000021|15=CHF|22=4|38=13|40=2|44=132|48=CH000000000|54=1|55=[N/A]|59=0|"
+ "60=20160803-12:55:41.866|207=XXXX|423=2|526=foo|528=P|"
// tag 20000 is not defined, tag 22000 is defined for NewOrderSingle in FIX44_Custom_Test.xml
+ "453=1|448=test|447=D|452=7|802=1|523=test|803=25|20000=0|22000=foobar|10=244|";

quickfix.fix44.NewOrderSingle nos = new quickfix.fix44.NewOrderSingle();
// using custom dictionary with user-defined tag 22000
final DataDictionary dataDictionary = new DataDictionary("FIX44_Custom_Test.xml");
dataDictionary.setCheckUserDefinedFields(false);
nos.fromString(newOrdersSingleString.replaceAll("\\|", "\001"), dataDictionary, true);
assertNull(nos.getException());
dataDictionary.validate(nos);

// defined tag should be set on the message
assertTrue(nos.isSetField(22000));
// undefined tag should not be set on the message
assertFalse(nos.isSetField(20000));
Group partyGroup = nos.getGroup(1, quickfix.field.NoPartyIDs.FIELD);
// undefined tag should be set on the subgroup instead
assertFalse(partyGroup.isSetField(20000));
assertTrue(partyGroup.getGroup(1, quickfix.field.NoPartySubIDs.FIELD).isSetField(20000));
}

private void assertHeaderField(Message message, String expectedValue, int field)
throws FieldNotFound {
assertEquals(expectedValue, message.getHeader().getString(field));
Expand Down
33 changes: 13 additions & 20 deletions quickfixj-core/src/test/java/quickfix/RepeatingGroupTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
import quickfix.field.SettlDate2;
import quickfix.field.Symbol;
import quickfix.fix44.Quote;
import quickfix.fix50sp2.QuoteRequest;

public class RepeatingGroupTest extends TestCase {

Expand Down Expand Up @@ -123,19 +124,19 @@ private quickfix.fix44.QuoteRequest.NoRelatedSym buildNestedGroupWithStandardFie
return gNoRelatedSym;
}

private quickfix.fix50.QuoteRequest.NoRelatedSym buildNestedGroupWithStandardFieldsFIX50(
private quickfix.fix50sp2.QuoteRequest.NoRelatedSym buildNestedGroupWithStandardFieldsFIX50SP2(
String settingValue) {
// The root group
final quickfix.fix50.QuoteRequest.NoRelatedSym gNoRelatedSym = new quickfix.fix50.QuoteRequest.NoRelatedSym();
final quickfix.fix50sp2.QuoteRequest.NoRelatedSym gNoRelatedSym = new quickfix.fix50sp2.QuoteRequest.NoRelatedSym();

// The nested group
final quickfix.fix50.QuoteRequest.NoRelatedSym.NoLegs nestedgroup = new quickfix.fix50.QuoteRequest.NoRelatedSym.NoLegs();
final quickfix.fix50sp2.QuoteRequest.NoRelatedSym.NoLegs nestedgroup = new quickfix.fix50sp2.QuoteRequest.NoRelatedSym.NoLegs();
nestedgroup.setField(new LegSymbol(settingValue));
gNoRelatedSym.addGroup(nestedgroup);

// Adding a second fake nested group to avoid being the case of having
// one element which is not relevant :-)
final quickfix.fix50.QuoteRequest.NoRelatedSym.NoLegs oneMoreNestedgroup = new quickfix.fix50.QuoteRequest.NoRelatedSym.NoLegs();
final quickfix.fix50sp2.QuoteRequest.NoRelatedSym.NoLegs oneMoreNestedgroup = new quickfix.fix50sp2.QuoteRequest.NoRelatedSym.NoLegs();
oneMoreNestedgroup.setField(new LegSymbol("Donald"));
gNoRelatedSym.addGroup(oneMoreNestedgroup);

Expand Down Expand Up @@ -305,7 +306,6 @@ public void testValidationWithNestedGroupAndStandardFields() throws InvalidMessa
gNoRelatedSym.setField(new Symbol("SYM00"));

quoteRequest.addGroup(gNoRelatedSym);

quoteRequest.addGroup(gNoRelatedSym);

final String sourceFIXString = quoteRequest.toString();
Expand All @@ -321,28 +321,25 @@ public void testValidationWithNestedGroupAndStandardFields() throws InvalidMessa
}

public void testValidationWithNestedGroupAndStandardFieldsFIX50SP2() throws InvalidMessage, ConfigError {
final quickfix.fix50.QuoteRequest quoteRequest = new quickfix.fix50.QuoteRequest();
final quickfix.fix50sp2.QuoteRequest quoteRequest = new quickfix.fix50sp2.QuoteRequest();

final quickfix.field.QuoteReqID gQuoteReqID = new quickfix.field.QuoteReqID();
gQuoteReqID.setValue("12342");
quoteRequest.setField(gQuoteReqID);

final quickfix.fix50.QuoteRequest.NoRelatedSym gNoRelatedSym = buildNestedGroupWithStandardFieldsFIX50("DEFAULT_VALUE");
final quickfix.fix50sp2.QuoteRequest.NoRelatedSym gNoRelatedSym = buildNestedGroupWithStandardFieldsFIX50SP2("DEFAULT_VALUE");
gNoRelatedSym.setField(new Symbol("SYM00"));
gNoRelatedSym.setField(new SettlDate2("20120801"));

quoteRequest.addGroup(gNoRelatedSym);

quoteRequest.addGroup(gNoRelatedSym);

final String sourceFIXString = quoteRequest.toString();
final DataDictionary fix50DataDictionary = new DataDictionary("FIX50SP2.xml");
final quickfix.fix50.QuoteRequest validatedMessage = (quickfix.fix50.QuoteRequest) buildValidatedMessage(
sourceFIXString, fix50DataDictionary);
String validateFIXString = null;
if (validatedMessage != null) {
validateFIXString = validatedMessage.toString();
}
final DataDictionary fix50sp2DataDictionary = new DataDictionary("FIX50SP2.xml");
final quickfix.fix50sp2.QuoteRequest validatedMessage = (quickfix.fix50sp2.QuoteRequest) messageFactory.create(FixVersions.FIX50SP2, QuoteRequest.MSGTYPE);
validatedMessage.fromString(sourceFIXString, fix50sp2DataDictionary, true);

String validateFIXString = validatedMessage.toString();

assertEquals("Message validation failed", sourceFIXString, validateFIXString);
assertEquals(2, validatedMessage.getGroupCount(gNoRelatedSym.getFieldTag()));
Expand Down Expand Up @@ -405,11 +402,7 @@ public void testGroupFieldsOrderWithCustomDataDictionary() throws InvalidMessage

assertNull("Invalid message", validatedMessage.getException());

String validatedFIXString = null;
if (validatedMessage != null) {
validatedFIXString = validatedMessage.toString();
}

String validatedFIXString = validatedMessage.toString();
assertEquals("Message validation failed",
MessageUtils.checksum(sourceFIXString), MessageUtils.checksum(validatedFIXString));
}
Expand Down
2 changes: 2 additions & 0 deletions quickfixj-core/src/test/resources/FIX44_Custom_Test.xml
Original file line number Diff line number Diff line change
Expand Up @@ -1206,6 +1206,7 @@
<field name="MoneyLaunderingStatus" required="N" />
<field name="RegistID" required="N" />
<field name="Designation" required="N" />
<field name="LargeField" required="N" />
</message>
<message name="ExecutionReport" msgtype="8" msgcat="app">
<field name="OrderID" required="Y" />
Expand Down Expand Up @@ -6649,5 +6650,6 @@
<field number="956" name="LegInterestAccrualDate" type="LOCALMKTDATE" />

<field number="5000" name="CustomTestField" type="INT" />
<field number="22000" name="LargeField" type="STRING" />
</fields>
</fix>