Skip to content

Commit fb074a6

Browse files
jacques0803chrjohn
andauthored
Enhanced logic around the ValidateFieldsOutOfOrder setting (#481)
* ValidateFieldsOutOfOrder Enhancements made to correctly parse Header fields found in the body of a message * ValidateFieldsOutOfOrder: Adding unit test for fix versions pre-FIXT11 * Update quickfixj-core/src/main/java/quickfix/Message.java Co-authored-by: Christoph John <christoph.john@macd.com>
1 parent 8fe4de7 commit fb074a6

File tree

2 files changed

+170
-11
lines changed

2 files changed

+170
-11
lines changed

quickfixj-core/src/main/java/quickfix/Message.java

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -604,7 +604,7 @@ void parse(String messageData, DataDictionary sessionDataDictionary,
604604

605605
try {
606606
parseHeader(sessionDataDictionary, doValidation);
607-
parseBody(applicationDataDictionary, doValidation);
607+
parseBody(sessionDataDictionary, applicationDataDictionary, doValidation);
608608
parseTrailer(sessionDataDictionary);
609609
if (doValidation && validateChecksum) {
610610
validateCheckSum(messageData);
@@ -670,34 +670,34 @@ private String getMsgType() throws InvalidMessage {
670670
}
671671
}
672672

673-
private void parseBody(DataDictionary dd, boolean doValidation) throws InvalidMessage {
674-
StringField field = extractField(dd, this);
673+
private void parseBody(DataDictionary sessionDataDictionary, DataDictionary applicationDataDictionary, boolean doValidation) throws InvalidMessage {
674+
StringField field = extractField(applicationDataDictionary, this);
675675
while (field != null) {
676676
if (isTrailerField(field.getField())) {
677677
pushBack(field);
678678
return;
679679
}
680680

681-
if (isHeaderField(field.getField())) {
681+
if (isHeaderField(field, sessionDataDictionary)) {
682682
// An acceptance test requires the sequence number to
683683
// be available even if the related field is out of order
684684
setField(header, field);
685685
// Group case
686-
if (dd != null && dd.isGroup(DataDictionary.HEADER_ID, field.getField())) {
687-
parseGroup(DataDictionary.HEADER_ID, field, dd, dd, header, doValidation);
686+
if (sessionDataDictionary != null && sessionDataDictionary.isGroup(DataDictionary.HEADER_ID, field.getField())) {
687+
parseGroup(DataDictionary.HEADER_ID, field, sessionDataDictionary, sessionDataDictionary, header, doValidation);
688688
}
689-
if (doValidation && dd != null && dd.isCheckFieldsOutOfOrder())
689+
if (doValidation && sessionDataDictionary != null && sessionDataDictionary.isCheckFieldsOutOfOrder())
690690
throw new FieldException(SessionRejectReason.TAG_SPECIFIED_OUT_OF_REQUIRED_ORDER,
691691
field.getTag());
692692
} else {
693693
setField(this, field);
694694
// Group case
695-
if (dd != null && dd.isGroup(getMsgType(), field.getField())) {
696-
parseGroup(getMsgType(), field, dd, dd, this, doValidation);
695+
if (applicationDataDictionary != null && applicationDataDictionary.isGroup(getMsgType(), field.getField())) {
696+
parseGroup(getMsgType(), field, applicationDataDictionary, applicationDataDictionary, this, doValidation);
697697
}
698698
}
699699

700-
field = extractField(dd, this);
700+
field = extractField(applicationDataDictionary, this);
701701
}
702702
}
703703

@@ -770,7 +770,7 @@ private void parseGroup(String msgType, StringField field, DataDictionary dd, Da
770770
}
771771
} else {
772772
// QFJ-169/QFJ-791: handle unknown repeating group fields in the body
773-
if (!isTrailerField(tag) && !(DataDictionary.HEADER_ID.equals(msgType))) {
773+
if (!isTrailerField(tag) && !(DataDictionary.HEADER_ID.equals(msgType) || isHeaderField(field, dd))) {
774774
if (checkFieldValidation(parent, parentDD, field, msgType, doValidation, group)) {
775775
continue;
776776
}

quickfixj-core/src/test/java/quickfix/MessageTest.java

Lines changed: 159 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,9 @@
6363
import quickfix.field.MsgDirection;
6464
import quickfix.field.MsgSeqNum;
6565
import quickfix.field.MsgType;
66+
import quickfix.field.NoHops;
6667
import quickfix.field.NoOrders;
68+
import quickfix.field.NoSides;
6769
import quickfix.field.OrdStatus;
6870
import quickfix.field.OrdType;
6971
import quickfix.field.OrderID;
@@ -129,6 +131,7 @@
129131
import static org.junit.Assert.assertEquals;
130132
import static org.junit.Assert.assertFalse;
131133
import static org.junit.Assert.assertNotNull;
134+
import static org.junit.Assert.assertNotEquals;
132135
import static org.junit.Assert.assertNull;
133136
import static org.junit.Assert.assertTrue;
134137
import static org.junit.Assert.fail;
@@ -1966,6 +1969,162 @@ public void shouldConvertToXMLWithIndent() {
19661969
" <trailer/>\n" + "</message>\n", xml);
19671970
}
19681971

1972+
@Test
1973+
public void testValidateFieldsOutOfOrderFIXT11() throws Exception {
1974+
final DataDictionary sessDictionary = DataDictionaryTest.getDictionary("FIXT11.xml");
1975+
final DataDictionary appDictionary = DataDictionaryTest.getDictionary("FIX50SP2.xml");
1976+
assertNotNull(sessDictionary);
1977+
assertNotNull(appDictionary);
1978+
assertNotEquals(appDictionary.getVersion(), sessDictionary.getVersion());
1979+
1980+
final String orderedData = "8=FIXT.1.1\u00019=561\u000135=AE\u0001" +
1981+
"34=545\u000149=SENDER\u000152=20220210-02:44:00.820\u000156=TARGET\u0001115=ON_BHEHALF\u0001" +
1982+
"1128=9\u0001" +
1983+
"627=2\u0001" +
1984+
"628=HOPID1\u0001629=20220414-15:22:54\u0001" +
1985+
"628=HOPID2\u0001629=20220414-15:22:54\u0001" +
1986+
"15=AUD\u000122=4\u000131=27\u000132=5000.000000000000\u000148=AU000000ANZ3\u000155=ANZ\u000160=20220210-02:43:27.796\u000164=20220214\u000175=20220210\u0001106=4075\u0001167=CS\u0001381=135000\u0001461=Exxxxx\u0001487=0\u0001762=1\u0001880=7533509260093686098:0#NORMAL#1644451200000000000\u00011003=1120000338\u00011015=0\u00011301=XASX\u0001" +
1987+
"552=2\u0001" +
1988+
"54=1\u0001453=1\u0001448=338-3\u0001447=D\u0001452=1\u00011=1040445\u0001576=1\u0001577=0\u000111=7533509260093758035\u0001" +
1989+
"54=2\u0001453=1\u0001448=338-3\u0001447=D\u0001452=1\u00011=1040445\u0001576=1\u0001577=0\u000111=7533509260093757876\u0001" +
1990+
"10=129\u0001";
1991+
final TradeCaptureReport tcrOrdered = new TradeCaptureReport();
1992+
tcrOrdered.fromString(orderedData, sessDictionary, appDictionary, true);
1993+
DataDictionary.validate(tcrOrdered, sessDictionary, appDictionary);
1994+
// As this is our reference message created with all validations switched on, make sure some message components
1995+
// are as expected
1996+
assertEquals(tcrOrdered.getHeader().getGroupCount(NoHops.FIELD), 2);
1997+
assertEquals(tcrOrdered.getGroupCount(NoSides.FIELD), 2);
1998+
1999+
sessDictionary.setCheckFieldsOutOfOrder(false);
2000+
appDictionary.setCheckFieldsOutOfOrder(false);
2001+
2002+
String unorderedData = "8=FIXT.1.1\u00019=561\u000135=AE\u0001" +
2003+
"15=AUD\u000122=4\u000131=27\u000132=5000.000000000000\u000148=AU000000ANZ3\u000155=ANZ\u000160=20220210-02:43:27.796\u000164=20220214\u000175=20220210\u0001106=4075\u0001167=CS\u0001381=135000\u0001461=Exxxxx\u0001487=0\u0001762=1\u0001880=7533509260093686098:0#NORMAL#1644451200000000000\u00011003=1120000338\u00011015=0\u00011301=XASX\u0001" +
2004+
"552=2\u0001" +
2005+
"54=1\u0001453=1\u0001448=338-3\u0001447=D\u0001452=1\u00011=1040445\u0001576=1\u0001577=0\u000111=7533509260093758035\u0001" +
2006+
"54=2\u0001453=1\u0001448=338-3\u0001447=D\u0001452=1\u00011=1040445\u0001576=1\u0001577=0\u000111=7533509260093757876\u0001" +
2007+
// Repeating Header Group, found just after a Repeating group within the body
2008+
"627=2\u0001628=HOPID1\u0001629=20220414-15:22:54\u0001628=HOPID2\u0001629=20220414-15:22:54\u0001" +
2009+
"34=545\u000149=SENDER\u000152=20220210-02:44:00.820\u000156=TARGET\u0001115=ON_BHEHALF\u00011128=9\u0001" +
2010+
"10=129\u0001";
2011+
TradeCaptureReport tcrUnOrdered = new TradeCaptureReport();
2012+
tcrUnOrdered.fromString(unorderedData, sessDictionary, appDictionary, true);
2013+
DataDictionary.validate(tcrUnOrdered, sessDictionary, appDictionary);
2014+
2015+
assertEquals(tcrOrdered.toString(), tcrUnOrdered.toString());
2016+
2017+
unorderedData = "8=FIXT.1.1\u00019=561\u000135=AE\u0001" +
2018+
"15=AUD\u000122=4\u000131=27\u000132=5000.000000000000\u000148=AU000000ANZ3\u000155=ANZ\u000160=20220210-02:43:27.796\u000164=20220214\u000175=20220210\u0001106=4075\u0001167=CS\u0001381=135000\u0001461=Exxxxx\u0001487=0\u0001762=1\u0001880=7533509260093686098:0#NORMAL#1644451200000000000\u00011003=1120000338\u00011015=0\u00011301=XASX\u0001" +
2019+
"552=2\u0001" +
2020+
"54=1\u0001453=1\u0001448=338-3\u0001447=D\u0001452=1\u00011=1040445\u0001576=1\u0001577=0\u000111=7533509260093758035\u0001" +
2021+
"54=2\u0001453=1\u0001448=338-3\u0001447=D\u0001452=1\u00011=1040445\u0001576=1\u0001577=0\u000111=7533509260093757876\u0001" +
2022+
// Header tag found just after Repeating group within the body
2023+
"34=545\u000149=SENDER\u000152=20220210-02:44:00.820\u000156=TARGET\u0001115=ON_BHEHALF\u00011128=9\u0001" +
2024+
"627=2\u0001628=HOPID1\u0001629=20220414-15:22:54\u0001628=HOPID2\u0001629=20220414-15:22:54\u0001" +
2025+
"10=129\u0001";
2026+
tcrUnOrdered = new TradeCaptureReport();
2027+
tcrUnOrdered.fromString(unorderedData, sessDictionary, appDictionary, true);
2028+
DataDictionary.validate(tcrUnOrdered, sessDictionary, appDictionary);
2029+
2030+
assertEquals(tcrOrdered.toString(), tcrUnOrdered.toString());
2031+
2032+
unorderedData = "8=FIXT.1.1\u00019=561\u000135=AE\u0001" +
2033+
"15=AUD\u000122=4\u000131=27\u000132=5000.000000000000\u000148=AU000000ANZ3\u000155=ANZ\u000160=20220210-02:43:27.796\u000164=20220214\u000175=20220210\u0001106=4075\u0001167=CS\u0001381=135000\u0001461=Exxxxx\u0001487=0\u0001762=1\u0001880=7533509260093686098:0#NORMAL#1644451200000000000\u00011003=1120000338\u00011015=0\u00011301=XASX\u0001" +
2034+
// Some Header fields found after body fields detected
2035+
"34=545\u000149=SENDER\u000152=20220210-02:44:00.820\u000156=TARGET\u0001115=ON_BHEHALF\u00011128=9\u0001" +
2036+
// Repeating Group
2037+
"552=2\u0001" +
2038+
"54=1\u0001453=1\u0001448=338-3\u0001447=D\u0001452=1\u00011=1040445\u0001576=1\u0001577=0\u000111=7533509260093758035\u0001" +
2039+
"54=2\u0001453=1\u0001448=338-3\u0001447=D\u0001452=1\u00011=1040445\u0001576=1\u0001577=0\u000111=7533509260093757876\u0001" +
2040+
// Some repeating Header tags
2041+
"627=2\u0001628=HOPID1\u0001629=20220414-15:22:54\u0001628=HOPID2\u0001629=20220414-15:22:54\u0001" +
2042+
"10=129\u0001";
2043+
tcrUnOrdered = new TradeCaptureReport();
2044+
tcrUnOrdered.fromString(unorderedData, sessDictionary, appDictionary, true);
2045+
DataDictionary.validate(tcrUnOrdered, sessDictionary, appDictionary);
2046+
2047+
assertEquals(tcrOrdered.toString(), tcrUnOrdered.toString());
2048+
2049+
}
2050+
2051+
@Test
2052+
public void testValidateFieldsOutOfOrderPreFIXT11() throws Exception {
2053+
final DataDictionary sessDictionary = DataDictionaryTest.getDictionary("FIX44.xml");
2054+
assertNotNull(sessDictionary);
2055+
2056+
final String orderedData =
2057+
"8=FIX.4.4\u00019=551\u000135=AE\u0001"
2058+
+ "34=545\u000149=SENDER\u000152=20220210-02:44:00.820\u000156=TARGET\u0001115=ON_BHEHALF\u0001"
2059+
+ "627=2\u0001628=HOPID1\u0001629=20220414-15:22:54\u0001628=HOPID2\u0001629=20220414-15:22:54\u0001"
2060+
+ "22=4\u000131=27\u000132=5000.000000000000\u000148=AU000000ANZ3\u000155=ANZ\u000160=20220210-02:43:27.796\u000164=20220214\u000175=20220210\u0001106=4075\u0001167=CS\u0001461=Exxxxx\u0001487=0\u0001570=N\u0001571=TradeReportID\u0001762=1\u0001880=7533509260093686098:0#NORMAL#1644451200000000000\u0001"
2061+
+ "552=2\u0001"
2062+
+ "54=1\u000137=OrderID1\u000111=7533509260093758035\u0001453=1\u0001448=338-3\u0001447=D\u0001452=1\u00011=1040445\u0001576=1\u0001577=0\u0001"
2063+
+ "54=2\u000137=OrderID2\u000111=7533509260093757876\u0001453=1\u0001448=338-3\u0001447=D\u0001452=1\u00011=1040445\u0001576=1\u0001577=0\u0001"
2064+
+ "10=191\u0001";
2065+
final TradeCaptureReport tcrOrdered = new TradeCaptureReport();
2066+
tcrOrdered.fromString(orderedData, sessDictionary, true);
2067+
DataDictionary.validate(tcrOrdered, sessDictionary, sessDictionary);
2068+
2069+
// As this is our reference message created with all validations switched on,
2070+
// make sure some message components
2071+
// are as expected
2072+
assertEquals(tcrOrdered.getHeader().getGroupCount(NoHops.FIELD), 2);
2073+
assertEquals(tcrOrdered.getGroupCount(NoSides.FIELD), 2);
2074+
2075+
sessDictionary.setCheckFieldsOutOfOrder(false);
2076+
2077+
String unorderedData = "8=FIX.4.4\u00019=551\u000135=AE\u0001"
2078+
+ "22=4\u000131=27\u000132=5000.000000000000\u000148=AU000000ANZ3\u000155=ANZ\u000160=20220210-02:43:27.796\u000164=20220214\u000175=20220210\u0001106=4075\u0001167=CS\u0001461=Exxxxx\u0001487=0\u0001570=N\u0001571=TradeReportID\u0001762=1\u0001880=7533509260093686098:0#NORMAL#1644451200000000000\u0001"
2079+
+ "552=2\u0001"
2080+
+ "54=1\u000137=OrderID1\u000111=7533509260093758035\u0001453=1\u0001448=338-3\u0001447=D\u0001452=1\u00011=1040445\u0001576=1\u0001577=0\u0001"
2081+
+ "54=2\u000137=OrderID2\u000111=7533509260093757876\u0001453=1\u0001448=338-3\u0001447=D\u0001452=1\u00011=1040445\u0001576=1\u0001577=0\u0001"
2082+
// Repeating Header Group, found just after a Repeating group within the body
2083+
+ "627=2\u0001628=HOPID1\u0001629=20220414-15:22:54\u0001628=HOPID2\u0001629=20220414-15:22:54\u0001"
2084+
+ "34=545\u000149=SENDER\u000152=20220210-02:44:00.820\u000156=TARGET\u0001115=ON_BHEHALF\u0001"
2085+
+ "10=191\u0001";
2086+
TradeCaptureReport tcrUnOrdered = new TradeCaptureReport();
2087+
tcrUnOrdered.fromString(unorderedData, sessDictionary, true);
2088+
DataDictionary.validate(tcrUnOrdered, sessDictionary, sessDictionary);
2089+
2090+
assertEquals(tcrOrdered.toString(), tcrUnOrdered.toString());
2091+
2092+
unorderedData = "8=FIX.4.4\u00019=551\u000135=AE\u0001"
2093+
+ "22=4\u000131=27\u000132=5000.000000000000\u000148=AU000000ANZ3\u000155=ANZ\u000160=20220210-02:43:27.796\u000164=20220214\u000175=20220210\u0001106=4075\u0001167=CS\u0001461=Exxxxx\u0001487=0\u0001570=N\u0001571=TradeReportID\u0001762=1\u0001880=7533509260093686098:0#NORMAL#1644451200000000000\u0001"
2094+
+ "552=2\u0001"
2095+
+ "54=1\u000137=OrderID1\u000111=7533509260093758035\u0001453=1\u0001448=338-3\u0001447=D\u0001452=1\u00011=1040445\u0001576=1\u0001577=0\u0001"
2096+
+ "54=2\u000137=OrderID2\u000111=7533509260093757876\u0001453=1\u0001448=338-3\u0001447=D\u0001452=1\u00011=1040445\u0001576=1\u0001577=0\u0001"
2097+
// Header tag found just after Repeating group within the body
2098+
+ "34=545\u000149=SENDER\u000152=20220210-02:44:00.820\u000156=TARGET\u0001115=ON_BHEHALF\u0001"
2099+
+ "627=2\u0001628=HOPID1\u0001629=20220414-15:22:54\u0001628=HOPID2\u0001629=20220414-15:22:54\u0001"
2100+
2101+
+ "10=191\u0001";
2102+
tcrUnOrdered = new TradeCaptureReport();
2103+
tcrUnOrdered.fromString(unorderedData, sessDictionary, true);
2104+
DataDictionary.validate(tcrUnOrdered, sessDictionary, sessDictionary);
2105+
2106+
assertEquals(tcrOrdered.toString(), tcrUnOrdered.toString());
2107+
2108+
unorderedData = "8=FIX.4.4\u00019=551\u000135=AE\u0001"
2109+
// Some body tags
2110+
+ "22=4\u000131=27\u000132=5000.000000000000\u000148=AU000000ANZ3\u000155=ANZ\u000160=20220210-02:43:27.796\u000164=20220214\u000175=20220210\u0001106=4075\u0001167=CS\u0001461=Exxxxx\u0001487=0\u0001570=N\u0001571=TradeReportID\u0001762=1\u0001880=7533509260093686098:0#NORMAL#1644451200000000000\u0001"
2111+
// The some Header tags
2112+
+ "34=545\u000149=SENDER\u000152=20220210-02:44:00.820\u000156=TARGET\u0001115=ON_BHEHALF\u0001"
2113+
// A Repeating body group
2114+
+ "552=2\u0001"
2115+
+ "54=1\u000137=OrderID1\u000111=7533509260093758035\u0001453=1\u0001448=338-3\u0001447=D\u0001452=1\u00011=1040445\u0001576=1\u0001577=0\u0001"
2116+
+ "54=2\u000137=OrderID2\u000111=7533509260093757876\u0001453=1\u0001448=338-3\u0001447=D\u0001452=1\u00011=1040445\u0001576=1\u0001577=0\u0001"
2117+
// Repeating Header Group
2118+
+ "627=2\u0001628=HOPID1\u0001629=20220414-15:22:54\u0001628=HOPID2\u0001629=20220414-15:22:54\u0001"
2119+
+ "10=191\u0001";
2120+
tcrUnOrdered = new TradeCaptureReport();
2121+
tcrUnOrdered.fromString(unorderedData, sessDictionary, true);
2122+
DataDictionary.validate(tcrUnOrdered, sessDictionary, sessDictionary);
2123+
2124+
assertEquals(tcrOrdered.toString(), tcrUnOrdered.toString());
2125+
2126+
}
2127+
19692128
private void assertHeaderField(Message message, String expectedValue, int field)
19702129
throws FieldNotFound {
19712130
assertEquals(expectedValue, message.getHeader().getString(field));

0 commit comments

Comments
 (0)