diff --git a/quickfixj-core/src/main/java/quickfix/Message.java b/quickfixj-core/src/main/java/quickfix/Message.java index eb9d56c21a..8f8a09dbd0 100644 --- a/quickfixj-core/src/main/java/quickfix/Message.java +++ b/quickfixj-core/src/main/java/quickfix/Message.java @@ -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; @@ -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. */ @@ -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); @@ -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, @@ -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); } } @@ -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(); @@ -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( @@ -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; } } @@ -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) { diff --git a/quickfixj-core/src/test/java/quickfix/MessageTest.java b/quickfixj-core/src/test/java/quickfix/MessageTest.java index f4be922d50..3a22107ca2 100644 --- a/quickfixj-core/src/test/java/quickfix/MessageTest.java +++ b/quickfixj-core/src/test/java/quickfix/MessageTest.java @@ -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)); diff --git a/quickfixj-core/src/test/java/quickfix/RepeatingGroupTest.java b/quickfixj-core/src/test/java/quickfix/RepeatingGroupTest.java index 1fd26df101..8c99963494 100644 --- a/quickfixj-core/src/test/java/quickfix/RepeatingGroupTest.java +++ b/quickfixj-core/src/test/java/quickfix/RepeatingGroupTest.java @@ -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 { @@ -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); @@ -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(); @@ -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())); @@ -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)); } diff --git a/quickfixj-core/src/test/resources/FIX44_Custom_Test.xml b/quickfixj-core/src/test/resources/FIX44_Custom_Test.xml index c62dd66e30..5585f8346f 100644 --- a/quickfixj-core/src/test/resources/FIX44_Custom_Test.xml +++ b/quickfixj-core/src/test/resources/FIX44_Custom_Test.xml @@ -1206,6 +1206,7 @@ + @@ -6649,5 +6650,6 @@ +